diff options
author | Oxore <oxore@protonmail.com> | 2023-05-07 20:38:17 +0300 |
---|---|---|
committer | Oxore <oxore@protonmail.com> | 2023-05-07 20:39:32 +0300 |
commit | f8a56f3c99fda938550ba6a1616c2fa4b41c19e9 (patch) | |
tree | 8b86ec58fe8773d2ae8853338ff6f95cce71d3f5 | |
parent | f475974aeee25082d6384f69212a9d237d394477 (diff) |
Impl basic sprite rendering
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | vdp.cpp | 181 | ||||
-rw-r--r-- | vdp.hpp | 16 |
3 files changed, 182 insertions, 16 deletions
@@ -1,4 +1,5 @@ *.o +*.out sim cmake[-_]build* compile_commands.json @@ -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 @@ -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{}; }; |