yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
emulator_render_service.cc
Go to the documentation of this file.
2
3#include <cstdio>
4
6#include "app/emu/snes.h"
7#include "rom/rom.h"
11#include "zelda3/game_data.h"
12
13namespace yaze {
14namespace emu {
15namespace render {
16
18 : rom_(rom), game_data_(game_data) {}
19
21
23 if (!rom_ || !rom_->is_loaded()) {
24 return absl::FailedPreconditionError("ROM not loaded");
25 }
26
27 // Create SNES instance
28 snes_ = std::make_unique<emu::Snes>();
29 const std::vector<uint8_t>& rom_data = rom_->vector();
30 snes_->Init(rom_data);
31
32 // Create save state manager
33 state_manager_ = std::make_unique<SaveStateManager>(snes_.get(), rom_);
34 auto status = state_manager_->Initialize();
35 if (!status.ok()) {
36 return status;
37 }
38
39 initialized_ = true;
40 return absl::OkStatus();
41}
42
44 if (!state_manager_) {
45 return absl::FailedPreconditionError("Service not initialized");
46 }
47 return state_manager_->GenerateAllBaselineStates();
48}
49
50absl::StatusOr<RenderResult> EmulatorRenderService::Render(
51 const RenderRequest& request) {
52 if (!initialized_) {
53 return absl::FailedPreconditionError("Service not initialized");
54 }
55
56 switch (request.type) {
60 return RenderDungeonObjectStatic(request);
61 }
62 return RenderDungeonObject(request);
63
65 return RenderSprite(request);
66
68 return RenderFullRoom(request);
69
70 default:
71 return absl::InvalidArgumentError("Unknown render target type");
72 }
73}
74
75absl::StatusOr<std::vector<RenderResult>> EmulatorRenderService::RenderBatch(
76 const std::vector<RenderRequest>& requests) {
77 std::vector<RenderResult> results;
78 results.reserve(requests.size());
79
80 for (const auto& request : requests) {
81 auto result = Render(request);
82 if (result.ok()) {
83 results.push_back(std::move(*result));
84 } else {
85 RenderResult error_result;
86 error_result.success = false;
87 error_result.error = std::string(result.status().message());
88 results.push_back(std::move(error_result));
89 }
90 }
91
92 return results;
93}
94
95absl::StatusOr<RenderResult> EmulatorRenderService::RenderDungeonObject(
96 const RenderRequest& req) {
97 RenderResult result;
98
99 // Load baseline room state
100 auto status = state_manager_->LoadState(StateType::kRoomLoaded, req.room_id);
101 if (!status.ok()) {
102 // Fall back to cold start if no state available
103 snes_->Reset(true);
104 }
105
106 // Load room context
108
109 // Inject room context
111 req.use_room_defaults ? room.blockset : req.blockset,
112 req.use_room_defaults ? room.palette : req.palette);
113
114 // Clear tilemap buffers
116
117 // Initialize tilemap pointers
119
120 // Mock APU ports
121 MockApuPorts();
122
123 // Lookup handler address
124 int data_offset = 0;
125 auto handler_result = LookupHandlerAddress(req.entity_id, &data_offset);
126 if (!handler_result.ok()) {
127 result.success = false;
128 result.error = std::string(handler_result.status().message());
129 return result;
130 }
131 int handler_addr = *handler_result;
132
133 // Calculate tilemap position
134 int tilemap_pos = (req.y * 0x80) + (req.x * 2);
135
136 // Execute handler
137 status = ExecuteHandler(handler_addr, data_offset, tilemap_pos);
138 if (!status.ok()) {
139 result.success = false;
140 result.error = std::string(status.message());
141 return result;
142 }
143
144 // Render PPU frame and extract pixels
147 result.width = 256;
148 result.height = 224;
149 result.success = true;
150 result.handler_address = handler_addr;
151
152 return result;
153}
154
156 const RenderRequest& req) {
157 RenderResult result;
158
159 // Load room for context
161 room.SetGameData(game_data_); // Ensure room has access to GameData
162
163 // Load room graphics
164 uint8_t blockset = req.use_room_defaults ? room.blockset : req.blockset;
165 room.LoadRoomGraphics(blockset);
167
168 // Get palette group and specific palette for color conversion
169 if (!game_data_) {
170 return absl::FailedPreconditionError("GameData not available");
171 }
172 auto& dungeon_main_pal_group = game_data_->palette_groups.dungeon_main;
173 uint8_t palette_id = req.use_room_defaults ? room.palette : req.palette;
174 if (palette_id >= dungeon_main_pal_group.size()) {
175 palette_id = 0;
176 }
177 auto palette = dungeon_main_pal_group[palette_id]; // For RGBA conversion
178
179 // Create background buffers for rendering
180 gfx::BackgroundBuffer bg1_buffer(512, 512);
181 gfx::BackgroundBuffer bg2_buffer(512, 512);
182
183 // Create object
184 zelda3::RoomObject obj(req.entity_id, req.x, req.y, req.size, 0);
185
186 // Create object drawer with room graphics buffer
187 const auto& gfx_buffer = room.get_gfx_buffer();
188 zelda3::ObjectDrawer drawer(rom_, req.room_id, gfx_buffer.data());
189 drawer.InitializeDrawRoutines();
190
191 // Draw the object (ObjectDrawer needs the full palette group)
192 auto status = drawer.DrawObject(obj, bg1_buffer, bg2_buffer, dungeon_main_pal_group);
193 if (!status.ok()) {
194 result.success = false;
195 result.error = std::string(status.message());
196 return result;
197 }
198
199 // Create output bitmap
200 std::vector<uint8_t> rgba_pixels(req.output_width * req.output_height * 4, 0);
201
202 // Ensure bitmaps are initialized before accessing pixel data
203 bg1_buffer.EnsureBitmapInitialized();
204 bg2_buffer.EnsureBitmapInitialized();
205
206 // Composite BG1 and BG2 into output
207 // BG2 is drawn first (lower priority), then BG1
208 const auto& bg2_pixels = bg2_buffer.bitmap().data();
209 const auto& bg1_pixels = bg1_buffer.bitmap().data();
210
211 for (int y = 0; y < req.output_height && y < 512; ++y) {
212 for (int x = 0; x < req.output_width && x < 512; ++x) {
213 int src_idx = y * 512 + x;
214 int dst_idx = (y * req.output_width + x) * 4;
215
216 uint8_t pixel = bg1_pixels[src_idx];
217 if (pixel == 0) {
218 pixel = bg2_pixels[src_idx];
219 }
220
221 // Convert indexed color to RGBA using palette
222 if (pixel > 0 && pixel < palette.size()) {
223 auto color = palette[pixel];
224 rgba_pixels[dst_idx + 0] = color.rgb().x; // R
225 rgba_pixels[dst_idx + 1] = color.rgb().y; // G
226 rgba_pixels[dst_idx + 2] = color.rgb().z; // B
227 rgba_pixels[dst_idx + 3] = 255; // A
228 } else {
229 // Transparent
230 rgba_pixels[dst_idx + 3] = 0;
231 }
232 }
233 }
234
235 result.rgba_pixels = std::move(rgba_pixels);
236 result.width = req.output_width;
237 result.height = req.output_height;
238 result.success = true;
239 result.used_static_fallback = true;
240
241 return result;
242}
243
244absl::StatusOr<RenderResult> EmulatorRenderService::RenderSprite(
245 const RenderRequest& req) {
246 RenderResult result;
247 result.success = false;
248 result.error = "Sprite rendering not yet implemented";
249 return result;
250}
251
252absl::StatusOr<RenderResult> EmulatorRenderService::RenderFullRoom(
253 const RenderRequest& req) {
254 RenderResult result;
255 result.success = false;
256 result.error = "Full room rendering not yet implemented";
257 return result;
258}
259
260void EmulatorRenderService::InjectRoomContext(int room_id, uint8_t blockset,
261 uint8_t palette) {
262 auto& ppu = snes_->ppu();
263
264 // Load room for graphics
266 room.SetGameData(game_data_); // Ensure room has access to GameData
267
268 // Load palette into CGRAM (palettes 0-5, 90 colors)
269 if (!game_data_) return;
270 auto dungeon_main_pal_group = game_data_->palette_groups.dungeon_main;
271 if (palette < dungeon_main_pal_group.size()) {
272 auto base_palette = dungeon_main_pal_group[palette];
273 for (size_t i = 0; i < base_palette.size() && i < 90; ++i) {
274 ppu.cgram[i] = base_palette[i].snes();
275 }
276 }
277
278 // Load sprite auxiliary palettes (palettes 6-7, indices 90-119)
279 const uint32_t kSpriteAuxPc = SnesToPc(rom_addresses::kSpriteAuxPalettes);
280 for (int i = 0; i < 30; ++i) {
281 uint32_t addr = kSpriteAuxPc + i * 2;
282 if (addr + 1 < rom_->size()) {
283 uint16_t snes_color = rom_->data()[addr] | (rom_->data()[addr + 1] << 8);
284 ppu.cgram[90 + i] = snes_color;
285 }
286 }
287
288 // Load graphics into VRAM
289 room.LoadRoomGraphics(blockset);
291 const auto& gfx_buffer = room.get_gfx_buffer();
292
293 // Convert to SNES planar format
294 std::vector<uint8_t> linear_data(gfx_buffer.begin(), gfx_buffer.end());
295 auto planar_data = ConvertLinear8bppToPlanar4bpp(linear_data);
296
297 // Copy to VRAM
298 for (size_t i = 0; i < planar_data.size() / 2 && i < 0x8000; ++i) {
299 ppu.vram[i] = planar_data[i * 2] | (planar_data[i * 2 + 1] << 8);
300 }
301
302 // Setup PPU registers
303 snes_->Write(0x002105, 0x09); // BG Mode 1
304 snes_->Write(0x002107, 0x40); // BG1 tilemap at VRAM $4000
305 snes_->Write(0x002108, 0x48); // BG2 tilemap at VRAM $4800
306 snes_->Write(0x00210B, 0x00); // BG1/2 chr at VRAM $0000
307 snes_->Write(0x00212C, 0x03); // Enable BG1+BG2
308 snes_->Write(0x002100, 0x0F); // Full brightness
309
310 // Set room ID in WRAM
311 snes_->Write(wram_addresses::kRoomId, room_id & 0xFF);
312 snes_->Write(wram_addresses::kRoomId + 1, (room_id >> 8) & 0xFF);
313}
314
316 auto& ppu = snes_->ppu();
317 if (!game_data_) return;
318 auto dungeon_main_pal_group = game_data_->palette_groups.dungeon_main;
319
320 if (palette_id >= 0 &&
321 palette_id < static_cast<int>(dungeon_main_pal_group.size())) {
322 auto palette = dungeon_main_pal_group[palette_id];
323 for (size_t i = 0; i < palette.size() && i < 90; ++i) {
324 ppu.cgram[i] = palette[i].snes();
325 }
326 }
327}
328
330 // This is handled by InjectRoomContext for now
331}
332
334 // Initialize the 11 tilemap indirect pointers at $BF-$DD
335 for (int i = 0; i < 11; ++i) {
336 uint32_t wram_addr =
338 uint8_t lo = wram_addr & 0xFF;
339 uint8_t mid = (wram_addr >> 8) & 0xFF;
340 uint8_t hi = (wram_addr >> 16) & 0xFF;
341
342 uint8_t zp_addr = wram_addresses::kTilemapPointers[i];
343 snes_->Write(0x7E0000 | zp_addr, lo);
344 snes_->Write(0x7E0000 | (zp_addr + 1), mid);
345 snes_->Write(0x7E0000 | (zp_addr + 2), hi);
346 }
347}
348
350 for (uint32_t i = 0; i < wram_addresses::kTilemapBufferSize; i++) {
353 }
354}
355
357 auto& apu = snes_->apu();
358 apu.out_ports_[0] = 0xAA; // Ready signal
359 apu.out_ports_[1] = 0xBB;
360 apu.out_ports_[2] = 0x00;
361 apu.out_ports_[3] = 0x00;
362}
363
365 int object_id, int* data_offset) {
366 auto rom_data = rom_->data();
367 uint32_t data_table_snes = 0;
368 uint32_t handler_table_snes = 0;
369
370 if (object_id < 0x100) {
371 data_table_snes = rom_addresses::kType1DataTable + (object_id * 2);
372 handler_table_snes = rom_addresses::kType1HandlerTable + (object_id * 2);
373 } else if (object_id < 0x200) {
374 data_table_snes =
375 rom_addresses::kType2DataTable + ((object_id - 0x100) * 2);
376 handler_table_snes =
377 rom_addresses::kType2HandlerTable + ((object_id - 0x100) * 2);
378 } else {
379 data_table_snes =
380 rom_addresses::kType3DataTable + ((object_id - 0x200) * 2);
381 handler_table_snes =
382 rom_addresses::kType3HandlerTable + ((object_id - 0x200) * 2);
383 }
384
385 uint32_t data_table_pc = SnesToPc(data_table_snes);
386 uint32_t handler_table_pc = SnesToPc(handler_table_snes);
387
388 if (data_table_pc + 1 >= rom_->size() ||
389 handler_table_pc + 1 >= rom_->size()) {
390 return absl::OutOfRangeError("Object ID out of bounds");
391 }
392
393 *data_offset = rom_data[data_table_pc] | (rom_data[data_table_pc + 1] << 8);
394 int handler_addr =
395 rom_data[handler_table_pc] | (rom_data[handler_table_pc + 1] << 8);
396
397 if (handler_addr == 0x0000) {
398 return absl::NotFoundError("Object has no drawing routine");
399 }
400
401 return handler_addr;
402}
403
404absl::Status EmulatorRenderService::ExecuteHandler(int handler_addr,
405 int data_offset,
406 int tilemap_pos) {
407 auto& cpu = snes_->cpu();
408
409 // Setup CPU state
410 cpu.PB = 0x01; // Program bank
411 cpu.DB = 0x7E; // Data bank (WRAM)
412 cpu.D = 0x0000; // Direct page
413 cpu.SetSP(0x01FF); // Stack
414 cpu.status = 0x30; // M=1, X=1 (8-bit mode)
415 cpu.E = 0; // Native mode
416
417 cpu.X = data_offset;
418 cpu.Y = tilemap_pos;
419
420 // Setup STP trap for return detection
421 const uint16_t trap_addr = 0xFF00;
422 snes_->Write(0x01FF00, 0xDB); // STP opcode
423
424 // Push return address
425 uint16_t sp = cpu.SP();
426 snes_->Write(0x010000 | sp--, 0x01);
427 snes_->Write(0x010000 | sp--, (trap_addr - 1) >> 8);
428 snes_->Write(0x010000 | sp--, (trap_addr - 1) & 0xFF);
429 cpu.SetSP(sp);
430
431 cpu.PC = handler_addr;
432
433 // Execute until STP or timeout
434 int max_opcodes = 100000;
435 int opcodes = 0;
436 auto& apu = snes_->apu();
437
438 while (opcodes < max_opcodes) {
439 uint32_t current_addr = (cpu.PB << 16) | cpu.PC;
440 uint8_t current_opcode = snes_->Read(current_addr);
441 if (current_opcode == 0xDB) {
442 break;
443 }
444
445 // Refresh APU mock periodically
446 if ((opcodes & 0x3F) == 0) {
447 apu.out_ports_[0] = 0xAA;
448 apu.out_ports_[1] = 0xBB;
449 }
450
451 cpu.RunOpcode();
452 opcodes++;
453 }
454
455 if (opcodes >= max_opcodes) {
456 return absl::DeadlineExceededError("Handler execution timeout");
457 }
458
459 return absl::OkStatus();
460}
461
463 auto& ppu = snes_->ppu();
464
465 // Copy WRAM tilemaps to VRAM
466 for (uint32_t i = 0; i < 0x800; i++) {
467 uint8_t lo = snes_->Read(wram_addresses::kBG1TilemapBuffer + i * 2);
468 uint8_t hi = snes_->Read(wram_addresses::kBG1TilemapBuffer + i * 2 + 1);
469 ppu.vram[0x4000 + i] = lo | (hi << 8);
470 }
471 for (uint32_t i = 0; i < 0x800; i++) {
472 uint8_t lo = snes_->Read(wram_addresses::kBG2TilemapBuffer + i * 2);
473 uint8_t hi = snes_->Read(wram_addresses::kBG2TilemapBuffer + i * 2 + 1);
474 ppu.vram[0x4800 + i] = lo | (hi << 8);
475 }
476
477 // Render frame
478 ppu.HandleFrameStart();
479 for (int line = 0; line < 224; line++) {
480 ppu.RunLine(line);
481 }
482 ppu.HandleVblank();
483}
484
486 // The SNES has a 512x478 framebuffer, but we typically render 256x224
487 std::vector<uint8_t> rgba(256 * 224 * 4);
488
489 // Get pixels from PPU's pixel buffer
490 // PPU stores pixels in 16-bit SNES format, need to convert to RGBA
491 auto& ppu = snes_->ppu();
492
493 for (int y = 0; y < 224; ++y) {
494 for (int x = 0; x < 256; ++x) {
495 int idx = (y * 256 + x) * 4;
496
497 // Get the SNES color from the PPU's output
498 // This assumes the PPU has already rendered to its internal buffer
499 // For now, just fill with test pattern
500 rgba[idx + 0] = 0; // R
501 rgba[idx + 1] = 0; // G
502 rgba[idx + 2] = 0; // B
503 rgba[idx + 3] = 255; // A
504 }
505 }
506
507 return rgba;
508}
509
510} // namespace render
511} // namespace emu
512} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:24
const auto & vector() const
Definition rom.h:139
auto data() const
Definition rom.h:135
auto size() const
Definition rom.h:134
bool is_loaded() const
Definition rom.h:128
absl::StatusOr< RenderResult > RenderDungeonObjectStatic(const RenderRequest &req)
absl::Status ExecuteHandler(int handler_addr, int data_offset, int tilemap_pos)
absl::StatusOr< RenderResult > Render(const RenderRequest &request)
void InjectRoomContext(int room_id, uint8_t blockset, uint8_t palette)
absl::StatusOr< RenderResult > RenderDungeonObject(const RenderRequest &req)
absl::StatusOr< RenderResult > RenderFullRoom(const RenderRequest &req)
absl::StatusOr< std::vector< RenderResult > > RenderBatch(const std::vector< RenderRequest > &requests)
absl::StatusOr< RenderResult > RenderSprite(const RenderRequest &req)
std::unique_ptr< SaveStateManager > state_manager_
absl::StatusOr< int > LookupHandlerAddress(int object_id, int *data_offset)
EmulatorRenderService(Rom *rom, zelda3::GameData *game_data=nullptr)
Draws dungeon objects to background buffers using game patterns.
void InitializeDrawRoutines()
Initialize draw routine registry Must be called before drawing objects.
absl::Status DrawObject(const RoomObject &object, gfx::BackgroundBuffer &bg1, gfx::BackgroundBuffer &bg2, const gfx::PaletteGroup &palette_group, const DungeonState *state=nullptr, gfx::BackgroundBuffer *layout_bg1=nullptr)
Draw a room object to background buffers.
const std::array< uint8_t, 0x10000 > & get_gfx_buffer() const
Definition room.h:537
void CopyRoomGraphicsToBuffer()
Definition room.cc:421
void LoadRoomGraphics(uint8_t entrance_blockset=0xFF)
Definition room.cc:370
uint8_t blockset
Definition room.h:490
uint8_t palette
Definition room.h:492
void SetGameData(GameData *data)
Definition room.h:531
struct snes_color snes_color
SNES color in 15-bit RGB format (BGR555)
constexpr uint32_t kType3HandlerTable
constexpr uint32_t kType2HandlerTable
constexpr uint32_t kSpriteAuxPalettes
constexpr uint32_t kType1HandlerTable
uint32_t SnesToPc(uint32_t snes_addr)
std::vector< uint8_t > ConvertLinear8bppToPlanar4bpp(const std::vector< uint8_t > &linear_data)
Room LoadRoomFromRom(Rom *rom, int room_id)
Definition room.cc:177
SNES color in 15-bit RGB format (BGR555)
Definition yaze.h:218
std::vector< uint8_t > rgba_pixels
gfx::PaletteGroupMap palette_groups
Definition game_data.h:89