1+ // Code taken w/ modifications from https://github.com/RidgeX/ygba
2+ // Copyright (c) 2021 Ridge Shrubsall
3+ // SPDX-License-Identifier: BSD-3-Clause
4+
5+ #include " gpu.hpp"
6+ #include " gba.hpp"
7+ #include < iostream>
8+
9+ #define BIT (x, i ) (((x) >> (i)) & 1 )
10+ #define BITS (x, i, j ) (((x) >> (i)) & ((1 << ((j) - (i) + 1 )) - 1 ))
11+
12+ #define SCREEN_WIDTH 240
13+ #define SCREEN_HEIGHT 160
14+
15+ static u32 rgb555_to_rgb888 (u16 pixel) {
16+ int red = BITS (pixel, 0 , 4 );
17+ int green = BITS (pixel, 5 , 9 );
18+ int blue = BITS (pixel, 10 , 14 );
19+
20+ red = (red << 3 ) | (red >> 2 );
21+ green = (green << 3 ) | (green >> 2 );
22+ blue = (blue << 3 ) | (blue >> 2 );
23+
24+ return 0xff << 24 | blue << 16 | green << 8 | red;
25+ }
26+
27+ static bool read_bitmap_pixel (GameboyAdvanceHarness& gba, int x, int y, int mode, u16 & out) {
28+ auto vram = gba.get_top ().rootp ->GameboyAdvance__DOT__VRAM__DOT__mem ;
29+ auto io = gba.get_top ().rootp ->GameboyAdvance__DOT__IO__DOT__mem ;
30+
31+ int w = (mode == 5 ? 160 : 240 );
32+ int h = (mode == 5 ? 128 : 160 );
33+
34+ if (x >= w || y >= h)
35+ return false ;
36+
37+ if (mode == 3 ) {
38+ u32 addr = (y * 240 + x) * 2 ;
39+ out = *(u16 *)&vram[addr];
40+ return true ;
41+ }
42+
43+ if (mode == 4 ) {
44+ bool page = (io[0x000 ] & 0x10 ); // DISPCNT bit 4
45+ u32 base = page ? 0xA000 : 0x0000 ;
46+
47+ uint8_t index = vram[base + y * 240 + x];
48+ if (index == 0 )
49+ return false ;
50+
51+ auto palette = gba.get_top ().rootp ->GameboyAdvance__DOT__Palette__DOT__mem ;
52+ out = *(u16 *)&palette[index * 2 ];
53+ return true ;
54+ }
55+
56+ if (mode == 5 ) {
57+ bool page = (io[0x000 ] & 0x10 );
58+ u32 base = page ? 0xA000 : 0x0000 ;
59+
60+ u32 addr = base + (y * 160 + x) * 2 ;
61+ out = *(u16 *)&vram[addr];
62+ return true ;
63+ }
64+
65+ return false ;
66+ }
67+
68+ void render_frame (GameboyAdvanceHarness& gba, u32 framebuffer[160 ][240 ]) {
69+
70+ auto io = gba.get_top ().rootp ->GameboyAdvance__DOT__IO__DOT__mem ;
71+ int mode = io[0 ] & 0x7 ; // DISPCNT
72+
73+ for (int y = 0 ; y < 160 ; y++) {
74+ for (int x = 0 ; x < 240 ; x++) {
75+ u16 pixel;
76+
77+ if (read_bitmap_pixel (gba, x, y, mode, pixel)) {
78+ framebuffer[y][x] = rgb555_to_rgb888 (pixel);
79+ } else {
80+ framebuffer[y][x] = 0xFF000000 ; // black
81+ }
82+ }
83+ }
84+ }
85+
86+ #include < SDL2/SDL.h>
87+ #include < cstdint>
88+
89+ class SDLDisplay {
90+ public:
91+ static constexpr int WIDTH = 240 ;
92+ static constexpr int HEIGHT = 160 ;
93+
94+ bool init (int scale = 3 ) {
95+ if (SDL_Init (SDL_INIT_VIDEO) != 0 ) {
96+ return false ;
97+ }
98+
99+ window = SDL_CreateWindow (
100+ " GBA" ,
101+ SDL_WINDOWPOS_CENTERED,
102+ SDL_WINDOWPOS_CENTERED,
103+ WIDTH * scale,
104+ HEIGHT * scale,
105+ 0 );
106+
107+ renderer = SDL_CreateRenderer (window, -1 , SDL_RENDERER_ACCELERATED);
108+
109+ texture = SDL_CreateTexture (
110+ renderer,
111+ SDL_PIXELFORMAT_ARGB8888,
112+ SDL_TEXTUREACCESS_STREAMING,
113+ WIDTH,
114+ HEIGHT);
115+
116+ return window && renderer && texture;
117+ }
118+
119+ void draw (uint32_t framebuffer[HEIGHT][WIDTH]) {
120+ SDL_UpdateTexture (texture, nullptr , framebuffer, WIDTH * sizeof (uint32_t ));
121+
122+ SDL_RenderClear (renderer);
123+ SDL_RenderCopy (renderer, texture, nullptr , nullptr );
124+ SDL_RenderPresent (renderer);
125+ }
126+
127+ bool process_events () {
128+ SDL_Event e;
129+ while (SDL_PollEvent (&e)) {
130+ if (e.type == SDL_QUIT)
131+ return false ;
132+ }
133+ return true ;
134+ }
135+
136+ ~SDLDisplay () {
137+ if (texture)
138+ SDL_DestroyTexture (texture);
139+ if (renderer)
140+ SDL_DestroyRenderer (renderer);
141+ if (window)
142+ SDL_DestroyWindow (window);
143+ SDL_Quit ();
144+ }
145+
146+ private:
147+ SDL_Window* window = nullptr ;
148+ SDL_Renderer* renderer = nullptr ;
149+ SDL_Texture* texture = nullptr ;
150+ };
151+
152+ void run_with_display (GameboyAdvanceHarness& gba) {
153+ const int WIDTH = 240 ;
154+ const int HEIGHT = 160 ;
155+ const int SCALE = 3 ;
156+ const int CYCLES_PER_FRAME = 280896 ;
157+
158+ if (SDL_Init (SDL_INIT_VIDEO) != 0 ) {
159+ std::cerr << " SDL init failed\n " ;
160+ return ;
161+ }
162+
163+ SDL_Window* window = SDL_CreateWindow (
164+ " GBA" ,
165+ SDL_WINDOWPOS_CENTERED,
166+ SDL_WINDOWPOS_CENTERED,
167+ WIDTH * SCALE,
168+ HEIGHT * SCALE,
169+ 0 );
170+
171+ SDL_Renderer* renderer = SDL_CreateRenderer (window, -1 , SDL_RENDERER_ACCELERATED);
172+ SDL_Texture* texture = SDL_CreateTexture (
173+ renderer,
174+ SDL_PIXELFORMAT_ARGB8888,
175+ SDL_TEXTUREACCESS_STREAMING,
176+ WIDTH,
177+ HEIGHT);
178+
179+ if (!window || !renderer || !texture) {
180+ std::cerr << " SDL setup failed\n " ;
181+ return ;
182+ }
183+
184+ uint32_t framebuffer[HEIGHT][WIDTH];
185+ int cycle_counter = 0 ;
186+ bool running = true ;
187+
188+ while (running) {
189+ // --- Handle events ---
190+ SDL_Event e;
191+ while (SDL_PollEvent (&e)) {
192+ if (e.type == SDL_QUIT) {
193+ running = false ;
194+ }
195+ }
196+
197+ // --- Run emulation ---
198+ gba.tick ();
199+ cycle_counter++;
200+
201+ // --- Render once per frame ---
202+ if (cycle_counter >= CYCLES_PER_FRAME) {
203+ cycle_counter = 0 ;
204+
205+ render_frame (gba, framebuffer);
206+
207+ SDL_UpdateTexture (texture, nullptr , framebuffer, WIDTH * sizeof (uint32_t ));
208+
209+ SDL_RenderClear (renderer);
210+ SDL_RenderCopy (renderer, texture, nullptr , nullptr );
211+ SDL_RenderPresent (renderer);
212+
213+ // Optional: cap to ~60 FPS
214+ SDL_Delay (16 );
215+ }
216+ }
217+
218+ SDL_DestroyTexture (texture);
219+ SDL_DestroyRenderer (renderer);
220+ SDL_DestroyWindow (window);
221+ SDL_Quit ();
222+ }
0 commit comments