1 | // MIT License |
2 | |
3 | // Copyright (c) 2020 Vadim Grigoruk @nesbox // grigoruk@gmail.com |
4 | |
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy |
6 | // of this software and associated documentation files (the "Software"), to deal |
7 | // in the Software without restriction, including without limitation the rights |
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
9 | // copies of the Software, and to permit persons to whom the Software is |
10 | // furnished to do so, subject to the following conditions: |
11 | |
12 | // The above copyright notice and this permission notice shall be included in all |
13 | // copies or substantial portions of the Software. |
14 | |
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
21 | // SOFTWARE. |
22 | |
23 | #include "api.h" |
24 | #include "core.h" |
25 | #include "tilesheet.h" |
26 | |
27 | #include <assert.h> |
28 | #include <string.h> |
29 | #include <stdlib.h> |
30 | #include <stdio.h> |
31 | #include <ctype.h> |
32 | #include <stddef.h> |
33 | #include <time.h> |
34 | |
35 | #include "tic_assert.h" |
36 | |
37 | #ifdef _3DS |
38 | #include <3ds.h> |
39 | #endif |
40 | |
41 | static_assert(TIC_BANK_BITS == 3, "tic_bank_bits" ); |
42 | static_assert(sizeof(tic_map) < 1024 * 32, "tic_map" ); |
43 | static_assert(sizeof(tic_rgb) == 3, "tic_rgb" ); |
44 | static_assert(sizeof(tic_palette) == 48, "tic_palette" ); |
45 | static_assert(sizeof(((tic_vram *)0)->vars) == 4, "tic_vram vars" ); |
46 | static_assert(sizeof(tic_vram) == TIC_VRAM_SIZE, "tic_vram" ); |
47 | static_assert(sizeof(tic_ram) == TIC_RAM_SIZE, "tic_ram" ); |
48 | |
49 | u8 tic_api_peek(tic_mem* memory, s32 address, s32 bits) |
50 | { |
51 | if (address < 0) |
52 | return 0; |
53 | |
54 | const u8* ram = (u8*)memory->ram; |
55 | enum{RamBits = sizeof(tic_ram) * BITS_IN_BYTE}; |
56 | |
57 | switch(bits) |
58 | { |
59 | case 1: if(address < RamBits / 1) return tic_tool_peek1(ram, address); |
60 | case 2: if(address < RamBits / 2) return tic_tool_peek2(ram, address); |
61 | case 4: if(address < RamBits / 4) return tic_tool_peek4(ram, address); |
62 | case 8: if(address < RamBits / 8) return ram[address]; |
63 | } |
64 | |
65 | return 0; |
66 | } |
67 | |
68 | void tic_api_poke(tic_mem* memory, s32 address, u8 value, s32 bits) |
69 | { |
70 | if (address < 0) |
71 | return; |
72 | |
73 | tic_core* core = (tic_core*)memory; |
74 | u8* ram = (u8*)memory->ram; |
75 | enum{RamBits = sizeof(tic_ram) * BITS_IN_BYTE}; |
76 | |
77 | switch(bits) |
78 | { |
79 | case 1: if(address < RamBits / 1) tic_tool_poke1(ram, address, value); break; |
80 | case 2: if(address < RamBits / 2) tic_tool_poke2(ram, address, value); break; |
81 | case 4: if(address < RamBits / 4) tic_tool_poke4(ram, address, value); break; |
82 | case 8: if(address < RamBits / 8) ram[address] = value; break; |
83 | } |
84 | } |
85 | |
86 | u8 tic_api_peek4(tic_mem* memory, s32 address) |
87 | { |
88 | return tic_api_peek(memory, address, 4); |
89 | } |
90 | |
91 | u8 tic_api_peek1(tic_mem* memory, s32 address) |
92 | { |
93 | return tic_api_peek(memory, address, 1); |
94 | } |
95 | |
96 | void tic_api_poke1(tic_mem* memory, s32 address, u8 value) |
97 | { |
98 | tic_api_poke(memory, address, value, 1); |
99 | } |
100 | |
101 | u8 tic_api_peek2(tic_mem* memory, s32 address) |
102 | { |
103 | return tic_api_peek(memory, address, 2); |
104 | } |
105 | |
106 | void tic_api_poke2(tic_mem* memory, s32 address, u8 value) |
107 | { |
108 | tic_api_poke(memory, address, value, 2); |
109 | } |
110 | |
111 | void tic_api_poke4(tic_mem* memory, s32 address, u8 value) |
112 | { |
113 | tic_api_poke(memory, address, value, 4); |
114 | } |
115 | |
116 | void tic_api_memcpy(tic_mem* memory, s32 dst, s32 src, s32 size) |
117 | { |
118 | tic_core* core = (tic_core*)memory; |
119 | s32 bound = sizeof(tic_ram) - size; |
120 | |
121 | if (size >= 0 |
122 | && size <= sizeof(tic_ram) |
123 | && dst >= 0 |
124 | && src >= 0 |
125 | && dst <= bound |
126 | && src <= bound) |
127 | { |
128 | u8* base = (u8*)memory->ram; |
129 | memcpy(base + dst, base + src, size); |
130 | } |
131 | } |
132 | |
133 | void tic_api_memset(tic_mem* memory, s32 dst, u8 val, s32 size) |
134 | { |
135 | tic_core* core = (tic_core*)memory; |
136 | s32 bound = sizeof(tic_ram) - size; |
137 | |
138 | if (size >= 0 |
139 | && size <= sizeof(tic_ram) |
140 | && dst >= 0 |
141 | && dst <= bound) |
142 | { |
143 | u8* base = (u8*)memory->ram; |
144 | memset(base + dst, val, size); |
145 | } |
146 | } |
147 | |
148 | void tic_api_trace(tic_mem* memory, const char* text, u8 color) |
149 | { |
150 | tic_core* core = (tic_core*)memory; |
151 | core->data->trace(core->data->data, text ? text : "nil" , color); |
152 | } |
153 | |
154 | u32 tic_api_pmem(tic_mem* tic, s32 index, u32 value, bool set) |
155 | { |
156 | u32 old = tic->ram->persistent.data[index]; |
157 | |
158 | if (set) |
159 | tic->ram->persistent.data[index] = value; |
160 | |
161 | return old; |
162 | } |
163 | |
164 | void tic_api_exit(tic_mem* tic) |
165 | { |
166 | tic_core* core = (tic_core*)tic; |
167 | core->data->exit(core->data->data); |
168 | } |
169 | |
170 | static inline void sync(void* dst, void* src, s32 size, bool rev) |
171 | { |
172 | if(rev) |
173 | SWAP(dst, src, void*); |
174 | |
175 | memcpy(dst, src, size); |
176 | } |
177 | |
178 | void tic_api_sync(tic_mem* tic, u32 mask, s32 bank, bool toCart) |
179 | { |
180 | tic_core* core = (tic_core*)tic; |
181 | |
182 | static const struct { s32 bank; s32 ram; s32 size; u8 mask; } Sections[] = |
183 | { |
184 | #define TIC_SYNC_DEF(CART, RAM, ...) { offsetof(tic_bank, CART), offsetof(tic_ram, RAM), sizeof(tic_##CART), tic_sync_##CART }, |
185 | TIC_SYNC_LIST(TIC_SYNC_DEF) |
186 | #undef TIC_SYNC_DEF |
187 | }; |
188 | |
189 | enum { Count = COUNT_OF(Sections), Mask = (1 << Count) - 1 }; |
190 | |
191 | if (mask == 0) mask = Mask; |
192 | |
193 | mask &= ~core->state.synced & Mask; |
194 | |
195 | assert(bank >= 0 && bank < TIC_BANKS); |
196 | |
197 | for (s32 i = 0; i < Count; i++) |
198 | if(mask & Sections[i].mask) |
199 | sync((u8*)tic->ram + Sections[i].ram, (u8*)&tic->cart.banks[bank] + Sections[i].bank, Sections[i].size, toCart); |
200 | |
201 | core->state.synced |= mask; |
202 | } |
203 | |
204 | double tic_api_time(tic_mem* memory) |
205 | { |
206 | tic_core* core = (tic_core*)memory; |
207 | return (clock() - core->data->start) * 1000.0 / CLOCKS_PER_SEC; |
208 | } |
209 | |
210 | s32 tic_api_tstamp(tic_mem* memory) |
211 | { |
212 | tic_core* core = (tic_core*)memory; |
213 | return (s32)time(NULL); |
214 | } |
215 | |
216 | static bool compareMetatag(const char* code, const char* tag, const char* value, const char* ) |
217 | { |
218 | bool result = false; |
219 | |
220 | char* str = tic_tool_metatag(code, tag, comment); |
221 | |
222 | if (str) |
223 | { |
224 | result = strcmp(str, value) == 0; |
225 | free(str); |
226 | } |
227 | |
228 | return result; |
229 | } |
230 | |
231 | const tic_script_config* tic_core_script_config(tic_mem* memory) |
232 | { |
233 | FOR_EACH_LANG(it) |
234 | { |
235 | if(it->id == memory->cart.lang || compareMetatag(memory->cart.code.data, "script" , it->name, it->singleComment)) |
236 | return it; |
237 | } |
238 | FOR_EACH_LANG_END |
239 | |
240 | return Languages[0]; |
241 | } |
242 | |
243 | static void updateSaveid(tic_mem* memory) |
244 | { |
245 | memset(memory->saveid, 0, sizeof memory->saveid); |
246 | char* saveid = tic_tool_metatag(memory->cart.code.data, "saveid" , tic_core_script_config(memory)->singleComment); |
247 | if (saveid) |
248 | { |
249 | strncpy(memory->saveid, saveid, TIC_SAVEID_SIZE - 1); |
250 | free(saveid); |
251 | } |
252 | } |
253 | |
254 | static void soundClear(tic_mem* memory) |
255 | { |
256 | tic_core* core = (tic_core*)memory; |
257 | |
258 | for (s32 i = 0; i < TIC_SOUND_CHANNELS; i++) |
259 | { |
260 | static const tic_channel_data EmptyChannel = |
261 | { |
262 | .tick = -1, |
263 | .pos = NULL, |
264 | .index = -1, |
265 | .note = 0, |
266 | .volume = {0, 0}, |
267 | .speed = 0, |
268 | .duration = -1, |
269 | }; |
270 | |
271 | memcpy(&core->state.music.channels[i], &EmptyChannel, sizeof EmptyChannel); |
272 | memcpy(&core->state.sfx.channels[i], &EmptyChannel, sizeof EmptyChannel); |
273 | |
274 | memset(core->state.sfx.channels[i].pos = &memory->ram->sfxpos[i], -1, sizeof(tic_sfx_pos)); |
275 | memset(core->state.music.channels[i].pos = &core->state.music.sfxpos[i], -1, sizeof(tic_sfx_pos)); |
276 | } |
277 | |
278 | memset(&memory->ram->registers, 0, sizeof memory->ram->registers); |
279 | memset(memory->product.samples.buffer, 0, memory->product.samples.count * TIC80_SAMPLESIZE); |
280 | |
281 | tic_api_music(memory, -1, 0, 0, false, false, -1, -1); |
282 | } |
283 | |
284 | static void resetVbank(tic_mem* memory) |
285 | { |
286 | ZEROMEM(memory->ram->vram.vars); |
287 | |
288 | static const u8 DefaultMapping[] = { 0x10, 0x32, 0x54, 0x76, 0x98, 0xba, 0xdc, 0xfe }; |
289 | memcpy(memory->ram->vram.mapping, DefaultMapping, sizeof DefaultMapping); |
290 | memory->ram->vram.palette = memory->cart.bank0.palette.vbank0; |
291 | memory->ram->vram.blit.segment = TIC_DEFAULT_BLIT_MODE; |
292 | } |
293 | |
294 | static void font2ram(tic_mem* memory) |
295 | { |
296 | memory->ram->font = (tic_font) { |
297 | .regular = |
298 | { |
299 | .data = |
300 | { |
301 | #include "font.inl" |
302 | }, |
303 | { |
304 | { |
305 | .width = TIC_FONT_WIDTH, |
306 | .height = TIC_FONT_HEIGHT, |
307 | } |
308 | } |
309 | }, |
310 | |
311 | .alt = |
312 | { |
313 | .data = |
314 | { |
315 | #include "altfont.inl" |
316 | }, |
317 | { |
318 | { |
319 | .width = TIC_ALTFONT_WIDTH, |
320 | .height = TIC_FONT_HEIGHT, |
321 | } |
322 | } |
323 | }, |
324 | }; |
325 | } |
326 | |
327 | void tic_api_reset(tic_mem* memory) |
328 | { |
329 | tic_core* core = (tic_core*)memory; |
330 | |
331 | // keyboard state is critical and must be preserved across API resets. |
332 | // Often `tic_api_reset` is called to effect transitions between modes |
333 | // yet we still need to know when the key WAS pressed after the |
334 | // transition - to prevent it from counting as a second keypress. |
335 | // |
336 | // So why presev `now` not `previous`? this is most often called in |
337 | // the middle of a tick... so we preserve now, which during `tick_end` |
338 | // is copied to previous. This duplicates the prior behavior of |
339 | // `ram.input.keyboard` (which existing outside `state`). |
340 | u32 kb_now = core->state.keyboard.now.data; |
341 | ZEROMEM(core->state); |
342 | core->state.keyboard.now.data = kb_now; |
343 | tic_api_clip(memory, 0, 0, TIC80_WIDTH, TIC80_HEIGHT); |
344 | |
345 | resetVbank(memory); |
346 | |
347 | VBANK(memory, 1) |
348 | { |
349 | resetVbank(memory); |
350 | |
351 | // init VBANK1 palette with VBANK0 palette if it's empty |
352 | // for backward compatibility |
353 | if(!EMPTY(memory->cart.bank0.palette.vbank1.data)) |
354 | memcpy(&memory->ram->vram.palette, &memory->cart.bank0.palette.vbank1, sizeof(tic_palette)); |
355 | } |
356 | |
357 | memory->ram->input.mouse.relative = 0; |
358 | |
359 | soundClear(memory); |
360 | updateSaveid(memory); |
361 | font2ram(memory); |
362 | } |
363 | |
364 | static void cart2ram(tic_mem* memory) |
365 | { |
366 | font2ram(memory); |
367 | |
368 | enum |
369 | { |
370 | #define TIC_SYNC_DEF(NAME, _, INDEX) sync_##NAME = INDEX, |
371 | TIC_SYNC_LIST(TIC_SYNC_DEF) |
372 | #undef TIC_SYNC_DEF |
373 | count, |
374 | all = (1 << count) - 1, |
375 | noscreen = BITCLEAR(all, sync_screen) |
376 | }; |
377 | |
378 | // don't sync empty screen |
379 | tic_api_sync(memory, EMPTY(memory->cart.bank0.screen.data) ? noscreen : all, 0, false); |
380 | } |
381 | |
382 | static void tic_close_current_vm(tic_core* core) |
383 | { |
384 | // close previous VM if any |
385 | if(core->currentVM) |
386 | { |
387 | // printf("Closing VM of %s, %d\n", core->currentScript->name, core->currentVM); |
388 | core->currentScript->close( (tic_mem*)core ); |
389 | core->currentVM = NULL; |
390 | } |
391 | if (core->memory.ram == NULL) { |
392 | core->memory.ram = core->memory.base_ram; |
393 | } |
394 | } |
395 | |
396 | static bool tic_init_vm(tic_core* core, const char* code, const tic_script_config* config) |
397 | { |
398 | tic_close_current_vm(core); |
399 | // set current script config and init |
400 | core->currentScript = config; |
401 | bool done = config->init( (tic_mem*) core , code); |
402 | if(!done) |
403 | { |
404 | // if it couldn't init, make sure the VM is not left dirty by the implementation |
405 | core->currentVM = NULL; |
406 | } |
407 | else |
408 | { |
409 | //printf("Initialized VM of %s, %d\n", core->currentScript->name, core->currentVM); |
410 | } |
411 | return done; |
412 | } |
413 | |
414 | s32 tic_api_vbank(tic_mem* tic, s32 bank) |
415 | { |
416 | tic_core* core = (tic_core*)tic; |
417 | |
418 | s32 prev = core->state.vbank.id; |
419 | |
420 | switch(bank) |
421 | { |
422 | case 0: |
423 | case 1: |
424 | if(core->state.vbank.id != bank) |
425 | { |
426 | SWAP(tic->ram->vram, core->state.vbank.mem, tic_vram); |
427 | core->state.vbank.id = bank; |
428 | } |
429 | } |
430 | |
431 | return prev; |
432 | } |
433 | |
434 | void tic_core_tick(tic_mem* tic, tic_tick_data* data) |
435 | { |
436 | tic_core* core = (tic_core*)tic; |
437 | |
438 | core->data = data; |
439 | |
440 | if (!core->state.initialized) |
441 | { |
442 | const char* code = tic->cart.code.data; |
443 | |
444 | bool done = false; |
445 | const tic_script_config* config = tic_core_script_config(tic); |
446 | |
447 | if (strlen(code)) |
448 | { |
449 | cart2ram(tic); |
450 | |
451 | core->state.synced = 0; |
452 | tic->input.data = 0; |
453 | |
454 | if (compareMetatag(code, "input" , "mouse" , config->singleComment)) |
455 | tic->input.mouse = 1; |
456 | else if (compareMetatag(code, "input" , "gamepad" , config->singleComment)) |
457 | tic->input.gamepad = 1; |
458 | else if (compareMetatag(code, "input" , "keyboard" , config->singleComment)) |
459 | tic->input.keyboard = 1; |
460 | else tic->input.data = -1; // default is all enabled |
461 | |
462 | data->start = clock(); |
463 | |
464 | // TODO: does where to fetch code from need to be a config option so this isn't hard |
465 | // coded for just a single langage? perhaps change it later when we have a second script |
466 | // engine that uses BINARY? |
467 | if (strcmp(config->name,"wasm" )==0) { |
468 | code = tic->cart.binary.data; |
469 | } |
470 | |
471 | done = tic_init_vm(core, code, config); |
472 | } |
473 | else |
474 | { |
475 | core->data->error(core->data->data, "the code is empty" ); |
476 | } |
477 | |
478 | if (done) |
479 | { |
480 | config->boot(tic); |
481 | core->state.tick = config->tick; |
482 | core->state.callback = config->callback; |
483 | core->state.initialized = true; |
484 | } |
485 | else return; |
486 | } |
487 | |
488 | core->state.tick(tic); |
489 | } |
490 | |
491 | void tic_core_pause(tic_mem* memory) |
492 | { |
493 | tic_core* core = (tic_core*)memory; |
494 | |
495 | memcpy(&core->pause.state, &core->state, sizeof(tic_core_state_data)); |
496 | memcpy(&core->pause.ram, memory->ram, sizeof(tic_ram)); |
497 | core->pause.input = memory->input.data; |
498 | |
499 | if (core->data) |
500 | { |
501 | core->pause.time.start = core->data->start; |
502 | core->pause.time.paused = clock(); |
503 | } |
504 | } |
505 | |
506 | void tic_core_resume(tic_mem* memory) |
507 | { |
508 | tic_core* core = (tic_core*)memory; |
509 | |
510 | if (core->data) |
511 | { |
512 | memcpy(&core->state, &core->pause.state, sizeof(tic_core_state_data)); |
513 | memcpy(memory->ram, &core->pause.ram, sizeof(tic_ram)); |
514 | core->data->start = core->pause.time.start + clock() - core->pause.time.paused; |
515 | memory->input.data = core->pause.input; |
516 | } |
517 | } |
518 | |
519 | void tic_core_close(tic_mem* memory) |
520 | { |
521 | tic_core* core = (tic_core*)memory; |
522 | |
523 | core->state.initialized = false; |
524 | |
525 | tic_close_current_vm(core); |
526 | |
527 | blip_delete(core->blip.left); |
528 | blip_delete(core->blip.right); |
529 | |
530 | free(memory->product.screen); |
531 | free(memory->product.samples.buffer); |
532 | free(core); |
533 | } |
534 | |
535 | void tic_core_tick_start(tic_mem* memory) |
536 | { |
537 | tic_core* core = (tic_core*)memory; |
538 | tic_core_sound_tick_start(memory); |
539 | tic_core_tick_io(memory); |
540 | |
541 | // SECURITY: preserve the system keyboard/game controller input state |
542 | // (and restore it post-tick, see below) to prevent user cartridges |
543 | // from being able to corrupt and take control of the inputs in |
544 | // nefarious ways. |
545 | // |
546 | // Related: https://github.com/nesbox/TIC-80/issues/1785 |
547 | core->state.keyboard.now.data = core->memory.ram->input.keyboard.data; |
548 | core->state.gamepads.now.data = core->memory.ram->input.gamepads.data; |
549 | |
550 | core->state.synced = 0; |
551 | } |
552 | |
553 | void tic_core_tick_end(tic_mem* memory) |
554 | { |
555 | tic_core* core = (tic_core*)memory; |
556 | tic80_input* input = &core->memory.ram->input; |
557 | |
558 | core->state.gamepads.previous.data = input->gamepads.data; |
559 | // SECURITY: we do not use `memory.ram.input` here because it is |
560 | // untrustworthy since the cartridge could have modified it to |
561 | // inject artificial keyboard/gamepad events. |
562 | core->state.keyboard.previous.data = core->state.keyboard.now.data; |
563 | core->state.gamepads.previous.data = core->state.gamepads.now.data; |
564 | |
565 | tic_core_sound_tick_end(memory); |
566 | } |
567 | |
568 | // copied from SDL2 |
569 | static inline void memset4(void* dst, u32 val, u32 dwords) |
570 | { |
571 | #if defined(__GNUC__) && defined(i386) |
572 | s32 u0, u1, u2; |
573 | __asm__ __volatile__( |
574 | "cld \n\t" |
575 | "rep ; stosl \n\t" |
576 | : "=&D" (u0), "=&a" (u1), "=&c" (u2) |
577 | : "0" (dst), "1" (val), "2" (dwords) |
578 | : "memory" |
579 | ); |
580 | #else |
581 | u32 _n = (dwords + 3) / 4; |
582 | u32* _p = (u32*)dst; |
583 | u32 _val = (val); |
584 | if (dwords == 0) |
585 | return; |
586 | switch (dwords % 4) |
587 | { |
588 | case 0: do { |
589 | *_p++ = _val; |
590 | case 3: *_p++ = _val; |
591 | case 2: *_p++ = _val; |
592 | case 1: *_p++ = _val; |
593 | } while (--_n); |
594 | } |
595 | #endif |
596 | } |
597 | |
598 | static inline tic_vram* vbank0(tic_core* core) |
599 | { |
600 | return core->state.vbank.id ? &core->state.vbank.mem : &core->memory.ram->vram; |
601 | } |
602 | |
603 | static inline tic_vram* vbank1(tic_core* core) |
604 | { |
605 | return core->state.vbank.id ? &core->memory.ram->vram : &core->state.vbank.mem; |
606 | } |
607 | |
608 | static inline void updpal(tic_mem* tic, tic_blitpal* pal0, tic_blitpal* pal1) |
609 | { |
610 | tic_core* core = (tic_core*)tic; |
611 | *pal0 = tic_tool_palette_blit(&vbank0(core)->palette, core->screen_format); |
612 | *pal1 = tic_tool_palette_blit(&vbank1(core)->palette, core->screen_format); |
613 | } |
614 | |
615 | static inline void updbdr(tic_mem* tic, s32 row, u32* ptr, tic_blit_callback clb, tic_blitpal* pal0, tic_blitpal* pal1) |
616 | { |
617 | tic_core* core = (tic_core*)tic; |
618 | |
619 | if(clb.border) clb.border(tic, row, clb.data); |
620 | |
621 | if(clb.scanline) |
622 | { |
623 | if(row == 0) clb.scanline(tic, 0, clb.data); |
624 | else if(row > TIC80_MARGIN_TOP && row < (TIC80_HEIGHT + TIC80_MARGIN_TOP)) |
625 | clb.scanline(tic, row - TIC80_MARGIN_TOP, clb.data); |
626 | } |
627 | |
628 | if(clb.border || clb.scanline) |
629 | updpal(tic, pal0, pal1); |
630 | |
631 | memset4(ptr, pal0->data[vbank0(core)->vars.border], TIC80_FULLWIDTH); |
632 | } |
633 | |
634 | static inline u32 blitpix(tic_mem* tic, s32 offset0, s32 offset1, const tic_blitpal* pal0, const tic_blitpal* pal1) |
635 | { |
636 | tic_core* core = (tic_core*)tic; |
637 | u32 pix = tic_tool_peek4(vbank1(core)->screen.data, offset1); |
638 | |
639 | return pix != vbank1(core)->vars.clear |
640 | ? pal1->data[pix] |
641 | : pal0->data[tic_tool_peek4(vbank0(core)->screen.data, offset0)]; |
642 | } |
643 | |
644 | void tic_core_blit_ex(tic_mem* tic, tic_blit_callback clb) |
645 | { |
646 | tic_core* core = (tic_core*)tic; |
647 | |
648 | tic_blitpal pal0, pal1; |
649 | updpal(tic, &pal0, &pal1); |
650 | |
651 | s32 row = 0; |
652 | u32* rowPtr = tic->product.screen; |
653 | |
654 | #define UPDBDR() updbdr(tic, row, rowPtr, clb, &pal0, &pal1) |
655 | |
656 | for(; row != TIC80_MARGIN_TOP; ++row, rowPtr += TIC80_FULLWIDTH) |
657 | UPDBDR(); |
658 | |
659 | for(; row != TIC80_FULLHEIGHT - TIC80_MARGIN_BOTTOM; ++row) |
660 | { |
661 | UPDBDR(); |
662 | rowPtr += TIC80_MARGIN_LEFT; |
663 | |
664 | if(*(u16*)&vbank0(core)->vars.offset == 0 && *(u16*)&vbank1(core)->vars.offset == 0) |
665 | { |
666 | // render line without XY offsets |
667 | for(s32 x = (row - TIC80_MARGIN_TOP) * TIC80_WIDTH, end = x + TIC80_WIDTH; x != end; ++x) |
668 | *rowPtr++ = blitpix(tic, x, x, &pal0, &pal1); |
669 | } |
670 | else |
671 | { |
672 | // render line with XY offsets |
673 | enum{OffsetY = TIC80_HEIGHT - TIC80_MARGIN_TOP}; |
674 | s32 start0 = (row - vbank0(core)->vars.offset.y + OffsetY) % TIC80_HEIGHT * TIC80_WIDTH; |
675 | s32 start1 = (row - vbank1(core)->vars.offset.y + OffsetY) % TIC80_HEIGHT * TIC80_WIDTH; |
676 | s32 offsetX0 = vbank0(core)->vars.offset.x; |
677 | s32 offsetX1 = vbank1(core)->vars.offset.x; |
678 | |
679 | for(s32 x = TIC80_WIDTH; x != 2 * TIC80_WIDTH; ++x) |
680 | *rowPtr++ = blitpix(tic, (x - offsetX0) % TIC80_WIDTH + start0, |
681 | (x - offsetX1) % TIC80_WIDTH + start1, &pal0, &pal1); |
682 | } |
683 | |
684 | rowPtr += TIC80_MARGIN_RIGHT; |
685 | } |
686 | |
687 | for(; row != TIC80_FULLHEIGHT; ++row, rowPtr += TIC80_FULLWIDTH) |
688 | UPDBDR(); |
689 | |
690 | #undef UPDBDR |
691 | } |
692 | |
693 | static inline void scanline(tic_mem* memory, s32 row, void* data) |
694 | { |
695 | tic_core* core = (tic_core*)memory; |
696 | |
697 | if (core->state.initialized) |
698 | core->state.callback.scanline(memory, row, data); |
699 | } |
700 | |
701 | static inline void border(tic_mem* memory, s32 row, void* data) |
702 | { |
703 | tic_core* core = (tic_core*)memory; |
704 | |
705 | if (core->state.initialized) |
706 | core->state.callback.border(memory, row, data); |
707 | } |
708 | |
709 | void tic_core_blit(tic_mem* tic) |
710 | { |
711 | tic_core_blit_ex(tic, (tic_blit_callback){scanline, border, NULL}); |
712 | } |
713 | |
714 | tic_mem* tic_core_create(s32 samplerate, tic80_pixel_color_format format) |
715 | { |
716 | tic_core* core = (tic_core*)malloc(sizeof(tic_core)); |
717 | memset(core, 0, sizeof(tic_core)); |
718 | |
719 | tic80* product = &core->memory.product; |
720 | |
721 | core->screen_format = format; |
722 | core->memory.ram = (tic_ram*)malloc(TIC_RAM_SIZE); |
723 | core->memory.base_ram = core->memory.ram; |
724 | core->samplerate = samplerate; |
725 | |
726 | memset(core->memory.ram, 0, sizeof(tic_ram)); |
727 | #ifdef _3DS |
728 | // To feed texture data directly to the 3DS GPU, linearly allocated memory is required, which is |
729 | // not guaranteed by malloc. |
730 | // Additionally, allocate TIC80_FULLHEIGHT + 1 lines to minimize glitches in linear scaling mode. |
731 | product->screen = linearAlloc(TIC80_FULLWIDTH * (TIC80_FULLHEIGHT + 1) * sizeof(u32)); |
732 | #else |
733 | product->screen = malloc(TIC80_FULLWIDTH * TIC80_FULLHEIGHT * sizeof product->screen[0]); |
734 | #endif |
735 | product->samples.count = samplerate * TIC80_SAMPLE_CHANNELS / TIC80_FRAMERATE; |
736 | product->samples.buffer = malloc(product->samples.count * TIC80_SAMPLESIZE); |
737 | |
738 | core->blip.left = blip_new(samplerate / 10); |
739 | core->blip.right = blip_new(samplerate / 10); |
740 | |
741 | blip_set_rates(core->blip.left, CLOCKRATE, samplerate); |
742 | blip_set_rates(core->blip.right, CLOCKRATE, samplerate); |
743 | |
744 | tic_api_reset(&core->memory); |
745 | |
746 | return &core->memory; |
747 | } |
748 | |