diff options
-rw-r--r-- | graphics.cpp | 12 | ||||
-rw-r--r-- | vdp.cpp | 118 | ||||
-rw-r--r-- | vdp.hpp | 14 |
3 files changed, 120 insertions, 24 deletions
diff --git a/graphics.cpp b/graphics.cpp index 03379c5..0a6f699 100644 --- a/graphics.cpp +++ b/graphics.cpp @@ -18,8 +18,8 @@ Graphics::Graphics() _window = SDL_CreateWindow("Gut (SEGA MD/G emulator)", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, - VDP().render_width, - VDP().render_height, + VDP().kRenderWidth, + VDP().kRenderHeight, SDL_WINDOW_RESIZABLE); if (!_window) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't set create window: %s\n", SDL_GetError()); @@ -36,8 +36,8 @@ Graphics::Graphics() _renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, - VDP().render_width, - VDP().render_height); + VDP().kRenderWidth, + VDP().kRenderHeight); if (!_render_texture) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't set create texture: %s\n", SDL_GetError()); SDL_Quit(); @@ -74,9 +74,9 @@ void Graphics::Render(const VDP& vdp) // TODO probably should return and propagate error abort(); } - for (size_t row = 0; row < VDP().render_height; row++) { + for (size_t row = 0; row < VDP().kRenderHeight; row++) { uint32_t *dst = (uint32_t*)((uint8_t*)pixels + row * pitch); - memcpy(dst, buffer, VDP().render_width * sizeof(*dst)); + memcpy(dst, buffer + row * VDP().kRenderWidth, VDP().kRenderWidth * sizeof(*dst)); } SDL_UnlockTexture(_render_texture); SDL_RenderClear(_renderer); @@ -45,6 +45,32 @@ ((reg) = ((reg) & ~(MODESET2_M2_MASK << MODESET2_M2_SHIFT)) \ | ((v & MODESET2_M2_MASK) << MODESET2_M2_SHIFT)) +#define SCROLLA_ADDR_MASK (0x38) +#define SCROLLA_ADDR_SHIFT (10) +#define SCROLLA_ADDR_GET(reg) \ + ((((uint16_t)(reg)) & (SCROLLA_ADDR_MASK)) << SCROLLA_ADDR_SHIFT) +#define SCROLLA_ADDR_SET(reg, v) \ + ((reg) = ((reg) & ~SCROLLA_ADDR_MASK) \ + | (((uint16_t)(v) >> SCROLLA_ADDR_SHIFT) & SCROLLA_ADDR_MASK)) + +// 0: V32 CELL, 1: V64 CELL, 2: Prohibited, 3: V128 CELL +#define SCROLLSIZE_VCELL_MASK (3) +#define SCROLLSIZE_VCELL_SHIFT (4) +#define SCROLLSIZE_VCELL_GET(reg) \ + (((reg) & (SCROLLSIZE_VCELL_MASK << SCROLLSIZE_VCELL_SHIFT)) >> SCROLLSIZE_VCELL_SHIFT) +#define SCROLLSIZE_VCELL_SET(reg, v) \ + ((reg) = ((reg) & ~(SCROLLSIZE_VCELL_MASK << SCROLLSIZE_VCELL_SHIFT)) \ + | ((v & SCROLLSIZE_VCELL_MASK) << SCROLLSIZE_VCELL_SHIFT)) + +// 0: H32 CELL, 1: H64 CELL, 2: Prohibited, 3: H128 CELL +#define SCROLLSIZE_HCELL_MASK (3) +#define SCROLLSIZE_HCELL_SHIFT (0) +#define SCROLLSIZE_HCELL_GET(reg) \ + (((reg) & (SCROLLSIZE_HCELL_MASK << SCROLLSIZE_HCELL_SHIFT)) >> SCROLLSIZE_HCELL_SHIFT) +#define SCROLLSIZE_HCELL_SET(reg, v) \ + ((reg) = ((reg) & ~(SCROLLSIZE_HCELL_MASK << SCROLLSIZE_HCELL_SHIFT)) \ + | ((v & SCROLLSIZE_HCELL_MASK) << SCROLLSIZE_HCELL_SHIFT)) + #define DMASRCADRHIGH_DMD_MASK (3) #define DMASRCADRHIGH_DMD_SHIFT (6) #define DMASRCADRHIGH_DMD_GET(reg) \ @@ -53,6 +79,10 @@ ((reg) = ((reg) & ~(DMASRCADRHIGH_DMD_MASK << DMASRCADRHIGH_DMD_SHIFT)) \ | ((v & DMASRCADRHIGH_DMD_MASK) << DMASRCADRHIGH_DMD_SHIFT)) +static constexpr size_t kCellWidthPixels = 8; +static constexpr size_t kCellHeightPixels = 8; +static constexpr size_t kPixelsPerByte = 2; + uint32_t VDP::Read(const uint32_t offset, const enum bitness bitness) { uint32_t ret{}; @@ -139,6 +169,66 @@ void VDP::Write(const uint32_t offset, const enum bitness bitness, const uint32_ } } +static inline size_t scrollsize_cells_count(uint8_t xcell_value) +{ + assert(xcell_value <= 3); + // It actually returns 96 when xcell_value == 2, but it is prohibited and we + // just assume that this is not the case. + return (xcell_value + 1) * 32; +} + +static inline uint32_t RenderColorFromCRAM(uint16_t color) +{ + const uint32_t blue = ((color >> 9) & 7) << 5; + const uint32_t green = ((color >> 5) & 7) << 5; + const uint32_t red = ((color >> 1) & 7) << 5; + return 0xff000000 | (red << 16) | (green << 8) | blue; +} + +static inline uint32_t GetU32BE(const void *buf) +{ + return + (static_cast<uint32_t>(reinterpret_cast<const uint8_t*>(buf)[0]) << 24) | + (static_cast<uint32_t>(reinterpret_cast<const uint8_t*>(buf)[1]) << 16) | + (static_cast<uint32_t>(reinterpret_cast<const uint8_t*>(buf)[2]) << 8) | + (static_cast<uint32_t>(reinterpret_cast<const uint8_t*>(buf)[3]) << 0); +} + +static inline uint16_t GetU16BE(const void *buf) +{ + return + (static_cast<uint16_t>(reinterpret_cast<const uint8_t*>(buf)[0]) << 8) | + (static_cast<uint16_t>(reinterpret_cast<const uint8_t*>(buf)[1]) << 0); +} + +void VDP::renderScrollALine(const size_t line_index, const size_t hcell_count) +{ + const uint8_t scrolla_reg = _reg[static_cast<size_t>(RegID::kScrollAAddress)]; + const uint16_t scrolla_addr = SCROLLA_ADDR_GET(scrolla_reg); + const size_t cy = line_index / kCellHeightPixels; + const size_t y = line_index % kCellHeightPixels; + for (size_t cx = 0; cx < hcell_count; cx++) { + if (cx * kCellWidthPixels >= kRenderWidth) { + break; + } + const uint16_t cell_id = GetU16BE( + _vram + scrolla_addr + (cy * hcell_count + cx) * sizeof(uint16_t)); + const uint32_t val = GetU32BE( + _vram + (cell_id * kCellHeightPixels + y) * (kCellWidthPixels / kPixelsPerByte)); + for (size_t x = 0; x < kCellWidthPixels; x++) { + if (cx * kCellWidthPixels + x >= kRenderWidth) { + continue; + } + const size_t color_index = (val >> ((kCellWidthPixels - 1 - x) * 4)) & 0xf; + const uint16_t cram_color = GetU16BE(_cram + color_index * sizeof(uint16_t)); + const uint32_t color = RenderColorFromCRAM(cram_color); + const size_t render_offset = + ((cy * kCellHeightPixels + y) * kRenderWidth) + cx * kCellWidthPixels + x; + _rendered_buffer[render_offset] = color; + } + } +} + bool VDP::Scanline() { const uint16_t mode_set_2 = _reg[static_cast<size_t>(RegID::kModeSet2)]; @@ -146,20 +236,23 @@ bool VDP::Scanline() return true; } const uint16_t lines_per_screen = _status.pal_mode ? kLinesPerScreenPAL : kLinesPerScreenNTSC; - // TODO remove the `1 || ` part - if (1 || _lines_counter + 1 == lines_per_screen) { - for (size_t i = 0; i < render_width; i++) { + if (_lines_counter >= (int)kRenderHeight) { + // just render palette + for (size_t i = 0; i < kRenderWidth; i++) { const size_t color_index = (i * 2) % kCRAMSize; - const uint16_t cram_color = (((uint16_t)_cram[color_index]) << 8) | _cram[color_index + 1]; - const uint8_t blue = ((cram_color >> 9) & 7) << 4; - const uint8_t green = ((cram_color >> 5) & 7) << 4; - const uint8_t red = ((cram_color >> 1) & 7) << 4; - const uint32_t color = 0xff000000 | (red << 16) | (green << 8) | blue; - _rendered_buffer[render_width * _lines_counter + i] = color; + const uint16_t cram_color = GetU16BE(_cram +color_index * sizeof(uint16_t)); + const uint32_t color = RenderColorFromCRAM(cram_color); + _rendered_buffer[kRenderWidth * _lines_counter + i] = color; } } else { - for (size_t i = 0; i < render_width; i++) { - _rendered_buffer[render_width * _lines_counter + i] = 0xff000000; + // Render sprites + const uint8_t scrollsize_reg = _reg[static_cast<size_t>(RegID::kScrollSize)]; + const uint8_t vcell = SCROLLSIZE_VCELL_GET(scrollsize_reg); + const uint8_t hcell = SCROLLSIZE_HCELL_GET(scrollsize_reg); + // 2 is prohibited value, so we just won't render if it is set + if (vcell != 2 && hcell != 2) { + const size_t hcell_count = scrollsize_cells_count(hcell); + renderScrollALine(_lines_counter, hcell_count); } } _lines_counter++; @@ -329,11 +422,12 @@ void VDP::writeControl(const uint16_t value) reg_val); } _reg[reg_num] = reg_val; + // TODO print warnings of all invalid values or writes that ignore a mask } else if (!_control_write_second_word) { // Write first word // CD1 CD0 A13 A12 A11 A10 A9 A8 // A7 A6 A5 A4 A3 A2 A1 A0 - _address = value & 0x3f; + _address = value & 0x3fff; _address_mode = (value >> 14) & 0x3; _control_write_second_word = true; } @@ -10,18 +10,19 @@ class VDP { public: - VDP(const uint32_t base_address_a = VDP_START): base_address(base_address_a) {} + constexpr VDP(const uint32_t base_address_a = VDP_START): base_address(base_address_a) {} uint32_t Read(uint32_t offset, enum bitness); void Write(uint32_t offset, enum bitness, uint32_t value); bool Scanline(); // Returns true if display disabled or vblank happened void Reset(); - const uint32_t* GetRenderedBuffer() const { return _rendered_buffer; } + constexpr const uint32_t* GetRenderedBuffer() const { return _rendered_buffer; } const uint32_t base_address; - static constexpr size_t render_height = 224; - static constexpr size_t render_width = 320; - static constexpr size_t render_buffer_size = 320*224*4; + static constexpr size_t kRenderHeight = 224; + static constexpr size_t kRenderWidth = 320; + static constexpr size_t render_buffer_size = kRenderWidth * kRenderHeight * + sizeof(*reinterpret_cast<VDP*>(4)->GetRenderedBuffer()); private: struct StatusRegister { @@ -70,6 +71,7 @@ class VDP { kRegistersCount, ///< Keep it last }; + void renderScrollALine(size_t line_index, size_t hcell_count); 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); @@ -112,5 +114,5 @@ class VDP { uint8_t _vram[kVRAMSize]{}; uint8_t _cram[kCRAMSize]{}; uint8_t _vsram[kVSRAMSize]{}; - uint32_t _rendered_buffer[kLinesPerScreenNTSC * render_width]{}; + uint32_t _rendered_buffer[kLinesPerScreenNTSC * kRenderWidth]{}; }; |