From cd8ae0e4f628bd491fcaf1dd4778bc283d619065 Mon Sep 17 00:00:00 2001 From: Hannes Schulze Date: Fri, 31 Jan 2025 23:24:45 +0100 Subject: implement renderer --- lib/nes/app/graphics/CMakeLists.txt | 9 ++++ lib/nes/app/graphics/color.hh | 21 ++++++++ lib/nes/app/graphics/image-tile.hh | 29 ++++++++++ lib/nes/app/graphics/image-view.hh | 31 +++++++++++ lib/nes/app/graphics/mask-tile.hh | 30 +++++++++++ lib/nes/app/graphics/renderer.cc | 104 ++++++++++++++++++++++++++++++++++++ lib/nes/app/graphics/renderer.hh | 62 +++++++++++++++++++++ 7 files changed, 286 insertions(+) create mode 100644 lib/nes/app/graphics/CMakeLists.txt create mode 100644 lib/nes/app/graphics/color.hh create mode 100644 lib/nes/app/graphics/image-tile.hh create mode 100644 lib/nes/app/graphics/image-view.hh create mode 100644 lib/nes/app/graphics/mask-tile.hh create mode 100644 lib/nes/app/graphics/renderer.cc create mode 100644 lib/nes/app/graphics/renderer.hh (limited to 'lib/nes/app/graphics') diff --git a/lib/nes/app/graphics/CMakeLists.txt b/lib/nes/app/graphics/CMakeLists.txt new file mode 100644 index 0000000..81bd0ff --- /dev/null +++ b/lib/nes/app/graphics/CMakeLists.txt @@ -0,0 +1,9 @@ +target_sources( + nes + PRIVATE + color.hh + image-view.hh + mask-tile.hh + image-tile.hh + renderer.hh + renderer.cc) \ No newline at end of file diff --git a/lib/nes/app/graphics/color.hh b/lib/nes/app/graphics/color.hh new file mode 100644 index 0000000..572ac25 --- /dev/null +++ b/lib/nes/app/graphics/color.hh @@ -0,0 +1,21 @@ +#pragma once + +#include + +namespace nes::app +{ + /// One of the predefined colors in the color palette used for the UI. + enum class color : std::uint8_t + { + transparent, ///< No color. + background_primary, ///< The primary background color. + background_secondary, ///< A darker version of the background color. + foreground_primary, ///< The primary foreground color. + foreground_secondary, ///< A more subtle version of the foreground color. + accent_primary, ///< The primary accent color. + accent_secondary, ///< A darker version of the accent color. + fixed_white, ///< Just white. + fixed_gray, ///< Just gray. + fixed_black, ///< Just black. + }; +} // namespace nes::app \ No newline at end of file diff --git a/lib/nes/app/graphics/image-tile.hh b/lib/nes/app/graphics/image-tile.hh new file mode 100644 index 0000000..5633a0e --- /dev/null +++ b/lib/nes/app/graphics/image-tile.hh @@ -0,0 +1,29 @@ +#pragma once + +#include "nes/app/graphics/color.hh" + +namespace nes::app +{ + /// An 8x8 tile with color data. + /// + /// The tile is stored as a flattened two-dimensional array of 8x8 colors, stored in row-major format. + struct image_tile + { + color data[8 * 8]{}; + }; + + namespace tiles + { + inline constexpr auto test = image_tile + { + color{ 0 }, color{ 0 }, color{ 9 }, color{ 9 }, color{ 9 }, color{ 9 }, color{ 0 }, color{ 0 }, + color{ 0 }, color{ 9 }, color{ 9 }, color{ 8 }, color{ 8 }, color{ 9 }, color{ 9 }, color{ 0 }, + color{ 9 }, color{ 9 }, color{ 8 }, color{ 8 }, color{ 8 }, color{ 8 }, color{ 9 }, color{ 9 }, + color{ 9 }, color{ 8 }, color{ 8 }, color{ 9 }, color{ 9 }, color{ 8 }, color{ 8 }, color{ 9 }, + color{ 9 }, color{ 8 }, color{ 8 }, color{ 9 }, color{ 9 }, color{ 8 }, color{ 8 }, color{ 9 }, + color{ 9 }, color{ 9 }, color{ 8 }, color{ 8 }, color{ 8 }, color{ 8 }, color{ 9 }, color{ 9 }, + color{ 0 }, color{ 9 }, color{ 9 }, color{ 8 }, color{ 8 }, color{ 9 }, color{ 9 }, color{ 0 }, + color{ 0 }, color{ 0 }, color{ 9 }, color{ 9 }, color{ 9 }, color{ 9 }, color{ 0 }, color{ 0 }, + }; + } // namespace tiles +} // namespace nes::app \ No newline at end of file diff --git a/lib/nes/app/graphics/image-view.hh b/lib/nes/app/graphics/image-view.hh new file mode 100644 index 0000000..a9b8e94 --- /dev/null +++ b/lib/nes/app/graphics/image-view.hh @@ -0,0 +1,31 @@ +#pragma once + +namespace nes::app +{ + struct image_tile; + + /// Non-owning copy of a 2-dimensional block of image tiles, forming an image. + /// + /// This is similar in spirit to a string_view, but for image tiles. + class image_view + { + image_tile const* data_{ nullptr }; + unsigned width_{ 0 }; + unsigned height_{ 0 }; + + public: + explicit image_view() = default; + + explicit image_view(image_tile const* data, unsigned const width, unsigned const height) + : data_{ data } + , width_{ width } + , height_{ height } + { + } + + auto get_data() const -> image_tile const* { return data_; } + auto get_width() const -> unsigned { return width_; } + auto get_height() const -> unsigned { return height_; } + auto get(unsigned const x, unsigned const y) const -> image_tile const& { return data_[y * height_ + x]; } + }; +} // namespace nes::app \ No newline at end of file diff --git a/lib/nes/app/graphics/mask-tile.hh b/lib/nes/app/graphics/mask-tile.hh new file mode 100644 index 0000000..c811003 --- /dev/null +++ b/lib/nes/app/graphics/mask-tile.hh @@ -0,0 +1,30 @@ +#pragma once + +#include + +namespace nes::app +{ + /// An 8x8 tile without intrinsic coloring, only providing the alpha mask. + /// + /// The tile is stored as an array of 8 8-bit integers, where each bit represents a horizontal pixel in the + /// integer's row. + struct mask_tile + { + std::uint8_t data[8]{}; + }; + + namespace tiles + { + inline constexpr auto letter_a = mask_tile + { + 0b00111000, + 0b01101100, + 0b11000110, + 0b11000110, + 0b11111110, + 0b11000110, + 0b11000110, + 0b00000000, + }; + } // namespace tiles +} // namespace nes::app \ No newline at end of file diff --git a/lib/nes/app/graphics/renderer.cc b/lib/nes/app/graphics/renderer.cc new file mode 100644 index 0000000..c53ea12 --- /dev/null +++ b/lib/nes/app/graphics/renderer.cc @@ -0,0 +1,104 @@ +#include "nes/app/graphics/renderer.hh" +#include "nes/app/graphics/color.hh" +#include "nes/app/graphics/mask-tile.hh" +#include "nes/app/graphics/image-tile.hh" +#include "nes/app/graphics/image-view.hh" +#include "nes/common/display.hh" + +namespace nes::app +{ + renderer::renderer(display& display) + : display_{ display } + { + } + + auto renderer::render_image_tile(unsigned const x, unsigned const y, image_tile const tile) -> void + { + if (x >= width || y >= height) { return; } + + for (auto y_px = unsigned{ 0 }; y_px < 8; ++y_px) + { + for (auto x_px = unsigned{ 0 }; x_px < 8; ++x_px) + { + auto const c = tile.data[y_px * 8 + x_px]; + if (c != color::transparent) + { + display_.set(8 * x + x_px, 8 * y + y_px, resolve_color(c)); + } + } + } + } + + auto renderer::render_mask_tile(unsigned const x, unsigned const y, mask_tile const tile, color const c) -> void + { + if (c == color::transparent) { return; } + if (x >= width || y >= height) { return; } + + auto const resolved = resolve_color(c); + for (auto y_px = unsigned{ 0 }; y_px < 8; ++y_px) + { + auto row = tile.data[y_px]; + for (auto x_px = unsigned{ 0 }; x_px < 8; ++x_px) + { + if (row & 0b10000000) + { + display_.set(8 * x + x_px, 8 * y + y_px, resolved); + } + row <<= 1; + } + } + } + + auto renderer::render_image(unsigned const x, unsigned const y, image_view const image) -> void + { + for (auto y_img = unsigned{ 0 }; y_img < image.get_height(); ++y_img) + { + for (auto x_img = unsigned{ 0 }; x_img < image.get_width(); ++x_img) + { + render_image_tile(x + x_img, y + y_img, image.get(x, y)); + } + } + } + + auto renderer::render_rect( + unsigned const x, unsigned const y, unsigned const width, unsigned const height, color const c) -> void + { + if (c == color::transparent) { return; } + if (x >= width || y >= height) { return; } + + auto const resolved = resolve_color(c); + auto const x_stop = std::min(x + width, renderer::width); + auto const y_stop = std::min(y + width, renderer::height); + for (auto y_px = unsigned{ y * 8 }; y_px < y_stop * 8; ++y_px) + { + for (auto x_px = unsigned{ x * 8 }; x_px < x_stop * 8; ++x_px) + { + display_.set(x_px, y_px, resolved); + } + } + } + + auto renderer::render_fill(color const c) -> void + { + render_rect(0, 0, width, height, c); + } + + auto renderer::resolve_color(color const c) const -> rgb + { + switch (c) + { + case color::transparent: break; + case color::background_primary: return rgb::from_hex(0x004058); + case color::background_secondary: return rgb::from_hex(0x002B3B); + case color::foreground_primary: return rgb::from_hex(0xFCFCFC); + case color::foreground_secondary: return rgb::from_hex(0x008888); + case color::accent_primary: return rgb::from_hex(0xE45C10); + case color::accent_secondary: return rgb::from_hex(0x881400); + case color::fixed_white: return rgb::from_hex(0xFCFCFC); + case color::fixed_gray: return rgb::from_hex(0xBCBCBC); + case color::fixed_black: return rgb::from_hex(0x000000); + } + + return rgb::from_hex(0x000000); + } +} // namespace nes::app \ No newline at end of file diff --git a/lib/nes/app/graphics/renderer.hh b/lib/nes/app/graphics/renderer.hh new file mode 100644 index 0000000..7b46461 --- /dev/null +++ b/lib/nes/app/graphics/renderer.hh @@ -0,0 +1,62 @@ +#pragma once + +#include +#include + +namespace nes +{ + class display; + class rgb; +} // namespace nes + +namespace nes::app +{ + struct image_tile; + struct mask_tile; + class image_view; + enum class color : std::uint8_t; + + /// Renders tiles and other primitive shapes onto a display. + /// + /// The renderer sees the display as a canvas divided into 8x8 tiles. All elements (except shadows and borders) must + /// be arranged on this 8x8 grid. + class renderer + { + display& display_; + + public: + static constexpr auto width = unsigned{ 32 }; + static constexpr auto height = unsigned{ 30 }; + + explicit renderer(display&); + + renderer(renderer const&) = delete; + renderer(renderer&&) = delete; + auto operator=(renderer const&) -> renderer& = delete; + auto operator=(renderer&&) -> renderer& = delete; + + /// If set, add a drop shadow behind newly drawn opaque elements. + auto set_shadow(unsigned x_px, unsigned y_px, color) -> void; + /// If set, add an inner border inside newly drawn opaque elements. + auto set_border(unsigned px, color) -> void; + + /// Render a colored tile at the given tile coordinates. + auto render_image_tile(unsigned x, unsigned y, image_tile) -> void; + /// Render an alpha-only tile at the given tile coordinates with the given color. + auto render_mask_tile(unsigned x, unsigned y, mask_tile, color) -> void; + /// Render an image at the given tile coordinates. + auto render_image(unsigned x, unsigned y, image_view) -> void; + /// Render a string of text at the given tile coordinates (one tile per character). + auto render_text(unsigned x, unsigned y, std::string_view, color) -> void; + /// Render a string of text at the given tile coordinates, limiting its length to the given width and + /// ellipsizing afterward. + auto render_text(unsigned x, unsigned y, std::string_view, unsigned width, color) -> void; + /// Render a rectangle at the given tile coordinates with a given size (measured in tiles). + auto render_rect(unsigned x, unsigned y, unsigned width, unsigned height, color) -> void; + /// Completely fill the screen. + auto render_fill(color) -> void; + + private: + auto resolve_color(color) const -> rgb; + }; +} // namespace nes::app \ No newline at end of file -- cgit v1.3.1