diff options
| author | Hannes Schulze <haschu0103@outlook.de> | 2025-01-31 23:24:45 +0100 |
|---|---|---|
| committer | Hannes Schulze <haschu0103@outlook.de> | 2025-01-31 23:24:45 +0100 |
| commit | cd8ae0e4f628bd491fcaf1dd4778bc283d619065 (patch) | |
| tree | dea35210e2bfe21182cfec76511a84de9a23a2fa | |
| parent | 307e07d6513f9524b7fcab5134494bd175465cd1 (diff) | |
implement renderer
| -rw-r--r-- | lib/nes/app/CMakeLists.txt | 3 | ||||
| -rw-r--r-- | lib/nes/app/application.cc | 15 | ||||
| -rw-r--r-- | lib/nes/app/graphics/CMakeLists.txt | 9 | ||||
| -rw-r--r-- | lib/nes/app/graphics/color.hh | 21 | ||||
| -rw-r--r-- | lib/nes/app/graphics/image-tile.hh | 29 | ||||
| -rw-r--r-- | lib/nes/app/graphics/image-view.hh | 31 | ||||
| -rw-r--r-- | lib/nes/app/graphics/mask-tile.hh | 30 | ||||
| -rw-r--r-- | lib/nes/app/graphics/renderer.cc | 104 | ||||
| -rw-r--r-- | lib/nes/app/graphics/renderer.hh | 62 | ||||
| -rw-r--r-- | lib/nes/common/display.hh | 11 | ||||
| -rw-r--r-- | lib/nes/sys/nes.cc | 3 | ||||
| -rw-r--r-- | lib/nes/sys/types/button-mask.hh | 16 |
12 files changed, 309 insertions, 25 deletions
diff --git a/lib/nes/app/CMakeLists.txt b/lib/nes/app/CMakeLists.txt index 18b2a2c..98a0490 100644 --- a/lib/nes/app/CMakeLists.txt +++ b/lib/nes/app/CMakeLists.txt @@ -4,4 +4,5 @@ target_sources( application.hh application.cc) -add_subdirectory(input)
\ No newline at end of file +add_subdirectory(input) +add_subdirectory(graphics)
\ No newline at end of file diff --git a/lib/nes/app/application.cc b/lib/nes/app/application.cc index db43854..ad3c1af 100644 --- a/lib/nes/app/application.cc +++ b/lib/nes/app/application.cc @@ -1,6 +1,11 @@ #include "nes/app/application.hh" #include "nes/app/input/input-device-keyboard.hh" #include "nes/app/input/input-device-controller.hh" +#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/common/display.hh" #include <iostream> namespace nes @@ -24,7 +29,7 @@ namespace nes auto application::frame(std::chrono::microseconds const elapsed_time) -> void { - console_.ref_controller_1().set_pressed(input_manager_.get_input_1().read_mask()); + /*console_.ref_controller_1().set_pressed(input_manager_.get_input_1().read_mask()); console_.ref_controller_2().set_pressed(input_manager_.get_input_2().read_mask()); console_.step(elapsed_time); @@ -33,6 +38,12 @@ namespace nes { std::cerr << "Invalid state: " << to_string(console_.get_status()) << std::endl; std::abort(); - } + }*/ + + auto r = app::renderer{ display_ }; + r.render_fill(app::color::background_primary); + r.render_mask_tile(10, 5, app::tiles::letter_a, app::color::foreground_primary); + r.render_image_tile(5, 5, app::tiles::test); + display_.switch_buffers(); } } // namespace nes
\ No newline at end of file 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 <cstdint> + +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 <cstdint> + +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 <string_view> +#include <cstdint> + +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 diff --git a/lib/nes/common/display.hh b/lib/nes/common/display.hh index ca78a85..85af915 100644 --- a/lib/nes/common/display.hh +++ b/lib/nes/common/display.hh @@ -26,17 +26,6 @@ namespace nes virtual auto set(unsigned x, unsigned y, rgb value) -> void = 0; /// Load the pixel value at the given position in the back buffer. virtual auto get(unsigned x, unsigned y) const -> rgb = 0; - /// Clear the entire back buffer with a color. - virtual auto clear(rgb const color) -> void - { - for (auto y = unsigned{ 0 }; y < height; ++y) - { - for (auto x = unsigned{ 0 }; x < width; ++x) - { - set(x, y, color); - } - } - } protected: explicit display() = default; diff --git a/lib/nes/sys/nes.cc b/lib/nes/sys/nes.cc index f922b7d..1504100 100644 --- a/lib/nes/sys/nes.cc +++ b/lib/nes/sys/nes.cc @@ -12,9 +12,6 @@ namespace nes , cpu_{ ppu_, cartridge_, controller_1_, controller_2_ } , status_{ cartridge_.get_status() } { - display_.clear(rgb{}); - display_.switch_buffers(); - display_.clear(rgb{}); } auto nes::step(std::chrono::microseconds const delta) -> void diff --git a/lib/nes/sys/types/button-mask.hh b/lib/nes/sys/types/button-mask.hh index 16b0dad..94c6a2b 100644 --- a/lib/nes/sys/types/button-mask.hh +++ b/lib/nes/sys/types/button-mask.hh @@ -32,13 +32,13 @@ namespace nes namespace buttons { - static constexpr auto a = button_mask::from_raw_value(1 << 0); - static constexpr auto b = button_mask::from_raw_value(1 << 1); - static constexpr auto select = button_mask::from_raw_value(1 << 2); - static constexpr auto start = button_mask::from_raw_value(1 << 3); - static constexpr auto up = button_mask::from_raw_value(1 << 4); - static constexpr auto down = button_mask::from_raw_value(1 << 5); - static constexpr auto left = button_mask::from_raw_value(1 << 6); - static constexpr auto right = button_mask::from_raw_value(1 << 7); + inline constexpr auto a = button_mask::from_raw_value(1 << 0); + inline constexpr auto b = button_mask::from_raw_value(1 << 1); + inline constexpr auto select = button_mask::from_raw_value(1 << 2); + inline constexpr auto start = button_mask::from_raw_value(1 << 3); + inline constexpr auto up = button_mask::from_raw_value(1 << 4); + inline constexpr auto down = button_mask::from_raw_value(1 << 5); + inline constexpr auto left = button_mask::from_raw_value(1 << 6); + inline constexpr auto right = button_mask::from_raw_value(1 << 7); } // namespace buttons } // namespace nes
\ No newline at end of file |
