summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOxore <oxore@protonmail.com>2023-05-07 20:38:17 +0300
committerOxore <oxore@protonmail.com>2023-05-07 20:39:32 +0300
commitf8a56f3c99fda938550ba6a1616c2fa4b41c19e9 (patch)
tree8b86ec58fe8773d2ae8853338ff6f95cce71d3f5
parentf475974aeee25082d6384f69212a9d237d394477 (diff)
Impl basic sprite rendering
-rw-r--r--.gitignore1
-rw-r--r--vdp.cpp181
-rw-r--r--vdp.hpp16
3 files changed, 182 insertions, 16 deletions
diff --git a/.gitignore b/.gitignore
index df4dc36..dfcdb04 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
*.o
+*.out
sim
cmake[-_]build*
compile_commands.json
diff --git a/vdp.cpp b/vdp.cpp
index f02c398..02cc21c 100644
--- a/vdp.cpp
+++ b/vdp.cpp
@@ -53,6 +53,14 @@
((reg) = ((reg) & ~SCROLLB_ADDR_MASK) \
| (((uint16_t)(v) >> SCROLLB_ADDR_SHIFT) & SCROLLB_ADDR_MASK))
+#define SPRITESTABLE_ADDR_MASK (0x7f)
+#define SPRITESTABLE_ADDR_SHIFT (9)
+#define SPRITESTABLE_ADDR_GET(reg) \
+ ((((uint16_t)(reg)) & (SPRITESTABLE_ADDR_MASK)) << SPRITESTABLE_ADDR_SHIFT)
+#define SPRITESTABLE_ADDR_SET(reg, v) \
+ ((reg) = ((reg) & ~SPRITESTABLE_ADDR_MASK) \
+ | (((uint16_t)(v) >> SPRITESTABLE_ADDR_SHIFT) & SPRITESTABLE_ADDR_MASK))
+
#define HSCROLL_TABLE_ADDR_MASK (0x3f)
#define HSCROLL_TABLE_ADDR_SHIFT (10)
#define HSCROLL_TABLE_ADDR_GET(reg) \
@@ -73,28 +81,49 @@
#define SCROLLSIZE_HCELL_GET(reg) REG_VAL_GET(reg, SCROLLSIZE_HCELL_MASK, SCROLLSIZE_HCELL_SHIFT)
#define SCROLLSIZE_HCELL_SET(reg, v) REG_VAL_SET(reg, SCROLLSIZE_HCELL_MASK, SCROLLSIZE_HCELL_SHIFT, v)
-#define BGCOLOR_COLOR_MASK (3)
+#define BGCOLOR_COLOR_MASK (0xf)
#define BGCOLOR_COLOR_SHIFT (0)
#define BGCOLOR_COLOR_GET(reg) REG_VAL_GET(reg, BGCOLOR_COLOR_MASK, BGCOLOR_COLOR_SHIFT)
#define BGCOLOR_COLOR_SET(reg, v) REG_VAL_SET(reg, BGCOLOR_COLOR_MASK, BGCOLOR_COLOR_SHIFT, v)
-#define BGCOLOR_PALETTE_MASK (3)
+#define BGCOLOR_PALETTE_MASK (2)
#define BGCOLOR_PALETTE_SHIFT (4)
#define BGCOLOR_PALETTE_GET(reg) REG_VAL_GET(reg, BGCOLOR_PALETTE_MASK, BGCOLOR_PALETTE_SHIFT)
#define BGCOLOR_PALETTE_SET(reg, v) REG_VAL_SET(reg, BGCOLOR_PALETTE_MASK, BGCOLOR_PALETTE_SHIFT, v)
+#define BGCOLOR_ALL_MASK (0x3f)
+#define BGCOLOR_ALL_SHIFT (0)
+#define BGCOLOR_ALL_GET(reg) REG_VAL_GET(reg, BGCOLOR_ALL_MASK, BGCOLOR_ALL_SHIFT)
+#define BGCOLOR_ALL_SET(reg, v) REG_VAL_SET(reg, BGCOLOR_ALL_MASK, BGCOLOR_ALL_SHIFT, v)
+
#define DMASRCADRHIGH_DMD_MASK (3)
#define DMASRCADRHIGH_DMD_SHIFT (6)
#define DMASRCADRHIGH_DMD_GET(reg) REG_VAL_GET(reg, DMASRCADRHIGH_DMD_MASK, DMASRCADRHIGH_DMD_SHIFT)
#define DMASRCADRHIGH_DMD_SET(reg, v) REG_VAL_SET(reg, DMASRCADRHIGH_DMD_MASK, DMASRCADRHIGH_DMD_SHIFT, v)
+/// RS0: Horizontal 40 (1) / Horizontal 32 (0) cell mode (should be same as RS1)
+#define MODESET4_RS0_MASK (1)
+#define MODESET4_RS0_SHIFT (7)
+#define MODESET4_RS0_GET(reg) REG_VAL_GET(reg, MODESET4_RS0_MASK, MODESET4_RS0_SHIFT)
+#define MODESET4_RS0_SET(reg, v) REG_VAL_SET(reg, MODESET4_RS0_MASK, MODESET4_RS0_SHIFT, v)
+
+/// RS1: Horizontal 40 (1) / Horizontal 32 (0) cell mode (should be same as RS0)
+#define MODESET4_RS1_MASK (1)
+#define MODESET4_RS1_SHIFT (0)
+#define MODESET4_RS1_GET(reg) REG_VAL_GET(reg, MODESET4_RS1_MASK, MODESET4_RS1_SHIFT)
+#define MODESET4_RS1_SET(reg, v) REG_VAL_SET(reg, MODESET4_RS1_MASK, MODESET4_RS1_SHIFT, v)
+
static constexpr size_t BitsCountToMask(const size_t bits)
{
- return bits ? ((1 << bits) | BitsCountToMask(bits - 1)) : 0;
+ return bits ? ((1 << (bits - 1)) | BitsCountToMask(bits - 1)) : 0;
}
-static constexpr size_t kCellWidthPixels = 8;
-static constexpr size_t kCellHeightPixels = 8;
+static constexpr size_t kSpriteHBorder = 128;
+static constexpr size_t kSpriteVBorder = 128;
+static constexpr size_t kSpriteHField = 512;
+static constexpr size_t kSpriteVField = 1024;
+static constexpr size_t kTileWidthPixels = 8;
+static constexpr size_t kTileHeightPixels = 8;
static constexpr size_t kPixelsPerByte = 2;
static constexpr size_t kBitsPerPixel = 4;
// Only 10 bits are effective in scroll speed value
@@ -243,30 +272,30 @@ void VDP::renderScrollLine(
const size_t vcell_count)
{
// TODO impl cell scroll and line scroll depending on kModeSet3 regester value
+ const size_t render_offset_y = line_index * kRenderWidth;
+ if (render_offset_y >= kRenderWidth * kRenderHeight) {
+ return;
+ }
const size_t vscroll =
GetU16BE(_vsram + sizeof(uint16_t) * plane_index) & kScrollSpeedMask;
const size_t hscroll =
GetU16BE(_vram + hscroll_table_addr + sizeof(uint16_t) * plane_index) & kScrollSpeedMask;
- const size_t cy = AddWrapping(line_index, vscroll, vcell_count * kCellHeightPixels) / kCellHeightPixels;
- const size_t y = AddWrapping(line_index, vscroll, vcell_count * kCellHeightPixels) % kCellHeightPixels;
+ const size_t cy = AddWrapping(line_index, vscroll, vcell_count * kTileHeightPixels) / kTileHeightPixels;
+ const size_t y = AddWrapping(line_index, vscroll, vcell_count * kTileHeightPixels) % kTileHeightPixels;
for (size_t cx = 0; cx < hcell_count; cx++) {
const uint16_t sprite_id = GetU16BE(
_vram + plane_addr + (cy * hcell_count + cx) * sizeof(uint16_t));
// Fetch whole 8 pixel (32 bit) line of sprite
const uint32_t val = GetU32BE(
- _vram + (sprite_id * kCellHeightPixels + y) * (kCellWidthPixels / kPixelsPerByte));
- for (size_t x = 0; x < kCellWidthPixels; x++) {
- const size_t render_offset_x = (cx * kCellWidthPixels + x + hscroll) %
- (hcell_count * kCellWidthPixels);
+ _vram + (sprite_id * kTileHeightPixels + y) * (kTileWidthPixels / kPixelsPerByte));
+ for (size_t x = 0; x < kTileWidthPixels; x++) {
+ const size_t render_offset_x = (cx * kTileWidthPixels + x + hscroll) %
+ (hcell_count * kTileWidthPixels);
if (render_offset_x >= kRenderWidth) {
continue;
}
- const size_t render_offset_y = line_index * kRenderWidth;
- if (render_offset_y >= kRenderWidth * kRenderHeight) {
- continue;
- }
const size_t color_index =
- (val >> ((kCellWidthPixels - 1 - x) * kBitsPerPixel)) & kPixelColorMask;
+ (val >> ((kTileWidthPixels - 1 - x) * kBitsPerPixel)) & kPixelColorMask;
const uint16_t bgcolor_reg = _reg[static_cast<size_t>(RegID::kBackgroundColor)];
const uint16_t bgcolor_color = BGCOLOR_COLOR_GET(bgcolor_reg);
const uint16_t bgcolor_palette = BGCOLOR_PALETTE_GET(bgcolor_reg);
@@ -301,6 +330,118 @@ void VDP::renderScrollALine(
renderScrollLine(line_index, plane_addr, hscroll_table_addr, 0, hcell_count, vcell_count);
}
+static inline SpriteAttributeEntry FetchSAE(
+ const uint8_t* vram, const uint16_t satable_addr, const uint8_t link)
+{
+ const uint16_t r0 = GetU16BE(vram + satable_addr + link * 8 + 0);
+ const uint16_t r1 = GetU16BE(vram + satable_addr + link * 8 + 2);
+ const uint16_t r2 = GetU16BE(vram + satable_addr + link * 8 + 4);
+ const uint16_t r3 = GetU16BE(vram + satable_addr + link * 8 + 6);
+ return SpriteAttributeEntry{
+ /* .vpos = */ static_cast<uint16_t>(r0 & BitsCountToMask(10)),
+ /* .hpos = */ static_cast<uint16_t>(r3 & BitsCountToMask(9)),
+ /* .tile_id = */ static_cast<uint16_t>(r2 & BitsCountToMask(11)),
+ /* .link = */ static_cast<uint8_t>(r1 & BitsCountToMask(7)),
+ /* .palette_id = */ static_cast<uint8_t>((r2 >> 13) & BitsCountToMask(2)),
+ /* .priority = */ static_cast<uint8_t>((r2 >> 15) & BitsCountToMask(1)),
+ /* .vreverse = */ static_cast<uint8_t>((r2 >> 12) & BitsCountToMask(1)),
+ /* .hreverse = */ static_cast<uint8_t>((r2 >> 11) & BitsCountToMask(1)),
+ /* .vsize = */ static_cast<uint8_t>(((r1 >> 8) & BitsCountToMask(2))),
+ /* .hsize = */ static_cast<uint8_t>(((r1 >> 10) & BitsCountToMask(2))),
+ };
+}
+
+void VDP::renderSpriteOnTheLine(
+ const SpriteAttributeEntry sae, const size_t line_index)
+{
+ const size_t render_offset_y = line_index * kRenderWidth;
+ if (render_offset_y >= kRenderWidth * kRenderHeight) {
+ return;
+ }
+ const size_t top = sae.vpos;
+ const size_t bottom = top + (sae.vsize + 1) * kTileHeightPixels;
+ const size_t left = sae.hpos;
+ const size_t right = sae.hpos + (sae.hsize + 1) * kTileWidthPixels;
+ // Test if sprite is outside of rendering range.
+ {
+ // We can test without thinking about wrapping since there is no sprite
+ // larger than 64 pixel by any dimension, but we have 128 pixels border
+ // on the left, right, bottom and top, which is twice as big as the
+ // biggest possible sprite.
+ if (top > line_index + kSpriteVBorder) {
+ return;
+ }
+ if (bottom <= line_index + kSpriteVBorder) {
+ return;
+ }
+ if (left > kRenderWidth + kSpriteHBorder) {
+ return;
+ }
+ if (right <= kSpriteHBorder) {
+ return;
+ }
+ }
+ const size_t tfirst = (line_index + kSpriteVBorder - top) / kTileHeightPixels;
+ const size_t tstep = sae.vsize + 1u;
+ const size_t y = (line_index + kSpriteVBorder - top) % kTileHeightPixels;
+ for (size_t tx = 0; tx < (sae.hsize + 1u); tx++) {
+ const uint16_t sprite_id = sae.tile_id + tfirst + tx * tstep;
+ // Fetch whole 8 pixel (32 bit) line of cell (tile)
+ const uint32_t val = GetU32BE(
+ _vram + (sprite_id * kTileHeightPixels + y) * (kTileWidthPixels / kPixelsPerByte));
+ for (size_t x = 0; x < kTileWidthPixels; x++) {
+ const size_t render_offset_x =
+ tx * kTileWidthPixels + x + sae.hpos - kSpriteHBorder;
+ if (render_offset_x >= kRenderWidth) {
+ continue;
+ }
+ const size_t color_index =
+ ((val >> ((kTileWidthPixels - 1 - x) * kBitsPerPixel)) & kPixelColorMask) |
+ (sae.palette_id << BGCOLOR_PALETTE_SHIFT);
+ const uint8_t bgcolor =
+ BGCOLOR_ALL_GET(_reg[static_cast<size_t>(RegID::kBackgroundColor)]);
+ if (color_index == bgcolor) {
+ // Do not render anything - background color means transparency
+ continue;
+ }
+ const uint16_t cram_color = GetU16BE(_cram + color_index * sizeof(uint16_t));
+ const uint32_t color = RenderColorFromCRAM(cram_color);
+ _rendered_buffer[render_offset_y + render_offset_x] = color;
+ }
+ }
+}
+
+void VDP::renderSpritesLine(const size_t line_index)
+{
+ const uint16_t sprites_table_addr = SPRITESTABLE_ADDR_GET(
+ _reg[static_cast<size_t>(RegID::kSpritesTableAddress)]);
+ const uint8_t modeset4_reg = _reg[static_cast<size_t>(RegID::kModeSet4)];
+ const uint8_t rs0 = MODESET4_RS0_GET(modeset4_reg);
+ const uint8_t rs1 = MODESET4_RS1_GET(modeset4_reg);
+ if (rs0 != rs1) {
+ // It is unknown what should happen, so nothing will.
+ return;
+ }
+ const size_t sprites_max_count = rs0 ? 40 : 32;
+ SpriteAttributeEntry sae{};
+ for (size_t i = 0; i < sprites_max_count; i++)
+ {
+ const uint8_t link = sae.link;
+ sae = FetchSAE(_vram, sprites_table_addr, link);
+ renderSpriteOnTheLine(sae, line_index);
+ if (sae.link == 0) {
+ break;
+ }
+ }
+ if (sae.link != 0 && !_unclosed_sprites_list) {
+ _unclosed_sprites_list = true;
+ // TODO warn of unclosed sprites list
+ } else if (sae.link == 0 && _unclosed_sprites_list) {
+ _unclosed_sprites_list = false;
+ // TODO info about sprites list become closed again
+ }
+}
+
bool VDP::Scanline()
{
const uint16_t mode_set_2 = _reg[static_cast<size_t>(RegID::kModeSet2)];
@@ -331,6 +472,7 @@ bool VDP::Scanline()
const size_t vcell_count = scrollsize_cells_count(vcell);
renderScrollBLine(_lines_counter, hcell_count, vcell_count);
renderScrollALine(_lines_counter, hcell_count, vcell_count);
+ renderSpritesLine(_lines_counter);
}
}
_lines_counter++;
@@ -500,6 +642,13 @@ void VDP::writeControl(const uint16_t value)
}
_reg[reg_num] = reg_val;
// TODO print warnings of all invalid values or writes that ignore a mask
+ // Some things to warn about:
+ // - MODESET4 RS0 does not equal to MODESET4 RS1
+ // - Sprite attribute table mask ignored
+ // - kScrollAAddress mask ignored
+ // - kScrollBAddress mask ignored
+ // - kScrollSize VCell is set to 2
+ // - kScrollSize HCell is set to 2
} else if (!_control_write_second_word) {
// Write first word
// CD1 CD0 A13 A12 A11 A10 A9 A8
diff --git a/vdp.hpp b/vdp.hpp
index e811422..f506ca4 100644
--- a/vdp.hpp
+++ b/vdp.hpp
@@ -8,6 +8,19 @@
#include <cstddef>
#include <cstring>
+struct SpriteAttributeEntry {
+ uint16_t vpos;
+ uint16_t hpos;
+ uint16_t tile_id;
+ uint8_t link;
+ uint8_t palette_id;
+ uint8_t priority;
+ uint8_t vreverse;
+ uint8_t hreverse;
+ uint8_t vsize;
+ uint8_t hsize;
+};
+
class VDP {
public:
constexpr VDP(const uint32_t base_address_a = VDP_START): base_address(base_address_a) {}
@@ -80,6 +93,8 @@ class VDP {
size_t vcell_count);
void renderScrollALine(size_t line_index, size_t hcell_count, size_t vcell_count);
void renderScrollBLine(size_t line_index, size_t hcell_count, size_t vcell_count);
+ void renderSpritesLine(size_t line_index);
+ void renderSpriteOnTheLine(SpriteAttributeEntry, size_t line_index);
void writeData(uint8_t address_mode, uint16_t address, uint16_t value);
void writeControl(uint16_t value);
uint16_t readData(uint8_t address_mode, uint16_t address);
@@ -123,4 +138,5 @@ class VDP {
uint8_t _cram[kCRAMSize]{};
uint8_t _vsram[kVSRAMSize]{};
uint32_t _rendered_buffer[kLinesPerScreenNTSC * kRenderWidth]{};
+ bool _unclosed_sprites_list{};
};