summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorHannes Schulze <haschu0103@outlook.de>2025-01-31 23:24:45 +0100
committerHannes Schulze <haschu0103@outlook.de>2025-01-31 23:24:45 +0100
commitcd8ae0e4f628bd491fcaf1dd4778bc283d619065 (patch)
treedea35210e2bfe21182cfec76511a84de9a23a2fa /lib
parent307e07d6513f9524b7fcab5134494bd175465cd1 (diff)
implement renderer
Diffstat (limited to 'lib')
-rw-r--r--lib/nes/app/CMakeLists.txt3
-rw-r--r--lib/nes/app/application.cc15
-rw-r--r--lib/nes/app/graphics/CMakeLists.txt9
-rw-r--r--lib/nes/app/graphics/color.hh21
-rw-r--r--lib/nes/app/graphics/image-tile.hh29
-rw-r--r--lib/nes/app/graphics/image-view.hh31
-rw-r--r--lib/nes/app/graphics/mask-tile.hh30
-rw-r--r--lib/nes/app/graphics/renderer.cc104
-rw-r--r--lib/nes/app/graphics/renderer.hh62
-rw-r--r--lib/nes/common/display.hh11
-rw-r--r--lib/nes/sys/nes.cc3
-rw-r--r--lib/nes/sys/types/button-mask.hh16
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