N64 QOI Encoder Demo 1.0.1
Encoder for N64 that saves it to SD card
Loading...
Searching...
No Matches
main.c
Go to the documentation of this file.
1
36
37#include <stdio.h>
38#include <stdlib.h>
39//#include <threads.h>
40
41#include <libdragon.h>
42
43#include "config.h"
44
45#include "qoi_enc_n64.h"
46#include "colorconv.h"
47
49#define ENC_BUFFER_SIZE 4096
50
66
69
75bool save_screenshot(surface_t* disp, const char* filename, uint32_t *bytesWritten) {
76
77 FILE* fp;
78
79 // Get the framebuffer data
80 uint16_t* framebuffer = (uint16_t*)disp->buffer;
81
82 qoi_desc_t desc;
83 qoi_enc_t enc;
84
85 uint8_t header[14];
86 uint8_t enc_buffer[ENC_BUFFER_SIZE];
87
88 if (disp == NULL || bytesWritten == NULL) return false;
89
90 qoi_set_dimensions(&desc, 320, 240); // Resolution of the N64 framebuffer
91
92 qoi_set_channels(&desc, 4); // RGBA format because the N64 framebuffer does have an alpha channel
93
94 qoi_set_colorspace(&desc, QOI_SRGB); // The N64 framebuffer uses sRGB color space
95
96 qoi_enc_init(&desc, &enc); // Initialize the encoder with the descriptor
97
98 fp = fopen(filename, "wb");
99
100 if (!fp) return false;
101
102 // Write the QOI header to SD card
103 write_qoi_header(&desc, header);
104 fwrite(header, 1, sizeof(header), fp);
105
106 qoi_enc_set_buffer(&enc, enc_buffer, ENC_BUFFER_SIZE, false);
107 //qoi_enc_alloc_buffer(&enc, ENC_BUFFER_SIZE);
108
109 *bytesWritten = 22; // 14+8=22
110
111 // Encode the pixel data
112 for (uint32_t px = 0; px < enc.len; px++)
113 {
114 uint32_t rgba = n64_color16_to_rgba32(read_be_u16(framebuffer[px]));
115 qoi_encode_chunk(&desc, &enc, &rgba);
116
117 if (enc.pixels_written >= enc.len) {
118 fwrite(enc.enc_buffer, 1, enc.buffer_offset, fp);
119 *bytesWritten += enc.buffer_offset;
120 break;
121 };
122
123 // Write the buffer to the file when it is almost full,
124 // leaving space for the padding bytes at the end of the file
125 if (enc.buffer_offset >= enc.buffer_len-8) {
126 fwrite(enc.enc_buffer, 1, enc.buffer_offset, fp);
127 *bytesWritten += enc.buffer_offset;
129 }
130
131 }
132
133 fwrite(QOI_PADDING, sizeof(uint8_t), 8, fp); // Write the padding bytes
134
135 //qoi_enc_free_buffer(&enc);
136
137 fclose(fp);
138
139 return true;
140}
141
146bool save_screenshot_null(surface_t* disp, uint32_t *bytesWritten) {
147 uint16_t* framebuffer;
148
149 qoi_desc_t desc;
150 qoi_enc_t enc;
151
152 uint8_t header[14];
153 uint8_t enc_buffer[ENC_BUFFER_SIZE];
154
155 if (disp == NULL || bytesWritten == NULL) return false;
156
157 // Get the framebuffer data
158 framebuffer = (uint16_t*)disp->buffer;
159
160 *bytesWritten = 22;
161
162 qoi_set_dimensions(&desc, 320, 240); // Resolution of the N64 framebuffer
163
164 qoi_set_channels(&desc, 4); // RGBA format because the N64 framebuffer does have an alpha channel
165
166 qoi_set_colorspace(&desc, QOI_SRGB); // The N64 framebuffer uses sRGB color space
167
168 qoi_enc_init(&desc, &enc); // Initialize the encoder with the descriptor
169
170 // Write the QOI header to buffer
171 write_qoi_header(&desc, header);
172
173 qoi_enc_set_buffer(&enc, enc_buffer, ENC_BUFFER_SIZE, false);
174 //qoi_enc_alloc_buffer(&enc, ENC_BUFFER_SIZE, false);
175
176 // Encode the pixel data
177 for (uint32_t px = 0; px < enc.len; px++)
178 {
179 uint32_t rgba = n64_color16_to_rgba32(read_be_u16(framebuffer[px]));
180 qoi_encode_chunk(&desc, &enc, &rgba);
181
182 if (enc.pixels_written >= enc.len) {
183 *bytesWritten += enc.buffer_offset;
184 break;
185
186 };
187
188 // Write the buffer to the file when it is almost full,
189 // leaving space for the padding bytes at the end of the file
190 if (enc.buffer_offset >= enc.buffer_len-8) {
191 *bytesWritten += enc.buffer_offset;
193 }
194
195 }
196
197 //qoi_enc_free_buffer(&enc);
198
199 return true;
200}
201
207bool save_screenshot_raw(surface_t* disp, const char* filename, uint32_t *bytesWritten) {
208 uint16_t* framebuffer;
209 FILE* fp;
210
211 if (disp == NULL || bytesWritten == NULL || filename == NULL) return false;
212
213 // Get the framebuffer data
214 framebuffer = (uint16_t*)disp->buffer;
215 *bytesWritten = 0;
216
217 fp = fopen(filename, "wb");
218 if (!fp) return false;
219
220 // Write the raw pixel data to the file
221 fwrite(framebuffer, sizeof(uint16_t), 320 * 240, fp);
222 *bytesWritten = 320 * 240 * sizeof(uint16_t);
223
224 fclose(fp);
225
226 return true;
227}
228
232int main(void) {
233
234 float x = 0.0f;
235 float y = 0.0f;
236
237 float speed = 0.1f;
238
239 bool sdCardExists;
240
241 bool toRight = true;
242 bool toDown = true;
243
244 bool showInfo = true;
245 bool showLogo = true;
246
247 uint64_t start, end;
248
249 float encodedTime = 0.0f;
250
251 float delta = 0.0f;
252
254 bool successfulSave = false;
255 surface_t lastSavedFrame = surface_alloc(FMT_RGBA16, 320, 240);
256
257 uint32_t bytesWritten = 0;
258
259 rdpq_font_t *font;
260 sprite_t* background, *logo;
261
262 //thrd_t thread;
263 //mtx_t mutex;
264
265 //mtx_init(&mutex, mtx_plain);
266
267 // Initialize libdragon subsystems
268 debug_init_emulog();
269 //console_init();
270
271 debug_init_usblog();
272 //console_set_debug(true);
273
274 timer_init();
275 joypad_init();
276
277 dfs_init(DFS_DEFAULT_LOCATION);
278
279 display_init(RESOLUTION_320x240, DEPTH_16_BPP, 2, GAMMA_NONE, FILTERS_RESAMPLE);
280
281 rdpq_init();
282
283 font = rdpq_font_load_builtin(FONT_BUILTIN_DEBUG_MONO);
284 rdpq_text_register_font(1, font);
285
286 logo = sprite_load("rom:/n64brew.sprite");
287 background = sprite_load("rom:/pm5544.sprite");
288 start = timer_ticks(), end = timer_ticks();
289
290 sdCardExists = debug_init_sdfs("sd:/", -1);
291
292 while (1) {
293 // Start drawing to the screen
294 surface_t* disp;
295
296 joypad_port_t port = JOYPAD_PORT_1;
297 joypad_buttons_t pressed, held;
298
299 joypad_poll();
300
301 pressed = joypad_get_buttons_pressed(port);
302 held = joypad_get_buttons_held(port);
303
304 end = timer_ticks();
305 delta = TIMER_MICROS(end - start) / 1000.0f; // Convert to milseconds
306 start = end;
307
308 while(!(disp = display_try_get())) {;}
309
310 if (showLogo) {
311 // Move the logo around the screen like a DVD logo to
312 // show that the screen is being updated and to have something to screenshot.
313 // The logo will bounce around the screen and change direction when it hits the edge of the screen.
314 if (toDown) {
315 if (y < 240.0f - (logo->height * 0.5f)) {
316 y += speed * delta;
317 }
318 else {
319 toDown = false;
320 }
321 }
322 else {
323 if (y > 0.0f) {
324 y -= speed * delta;
325 }
326 else {
327 toDown = true;
328 }
329 }
330
331 if (toRight) {
332 if (x < 320.0f - (logo->width * 0.5f)) {
333 x += speed * delta;
334 }
335 else {
336 toRight = false;
337 }
338 }
339 else {
340 if (x > 0.0f) {
341 x -= speed * delta;
342 }
343 else {
344 toRight = true;
345 }
346 }
347 }
348
349
350 rdpq_attach(disp, NULL);
351
352 rdpq_set_mode_copy(true);
353 rdpq_sprite_blit(background, 0, 0, NULL);
354
355 if (showLogo) {
356 rdpq_set_mode_copy(true);
357 rdpq_sprite_blit(logo, (int)x, (int)y, &( rdpq_blitparms_t ) {
358 .scale_x = 0.5f,
359 .scale_y = 0.5f
360 });
361 }
362
363
364 rdpq_set_mode_standard();
365
366 if (showInfo) {
367
368 //mutex_lock(&mutex);
369 rdpq_text_printf(&(rdpq_textparms_t) {
370 .width = 320-32,
371 .align = ALIGN_LEFT,
372 .wrap = WRAP_WORD,
373 }, 1, 32, 32, "Encoded in %.8f ms\nBytes written: %lu bytes", encodedTime, bytesWritten);
374
375 rdpq_text_printf(&(rdpq_textparms_t) {
376 .width = 320-32,
377 .align = ALIGN_LEFT,
378 .wrap = WRAP_WORD,
379 }, 1, 32, 60, "State: %s", scrType == SCREENSHOT_NEVER_TAKEN ? "No screenshot taken" :
380 scrType == SCREENSHOT_TYPE_NULL ? "Saved to null" :
381 scrType == SCREENSHOT_TYPE_RAW ? "Saved as raw" :
382 scrType == SCREENSHOT_TYPE_QOI ? "Saved as QOI" :
383 scrType == SCREENSHOT_FRAME_CAPTURED ? "Frame captured, not saved" :
384 scrType == SCREENSHOT_NO_SD_CARD ? "No SD card found" :
385 "Unknown"
386 );
387
388 rdpq_text_printf(&(rdpq_textparms_t) {
389 .width = 320-32,
390 .align = ALIGN_LEFT,
391 .wrap = WRAP_WORD,
392 }, 1, 32, 72, "Save successful: %s", successfulSave ? "Yes" : "No");
393
394 rdpq_text_printf(&(rdpq_textparms_t) {
395 .width = 320-32,
396 .align = ALIGN_LEFT,
397 .wrap = WRAP_WORD,
398 }, 1, 32, 200, "Version: %s\nRevision date: %s",
401 //mutex_unlock(&mutex);
402 }
403 if (pressed.start) {
404 showInfo ^= true;
405 }
406
407 if (pressed.d_down) {
408 showLogo ^= true;
409 }
410
411 if (pressed.z) {
412
413 // capture the current frame into a buffer for testing
414 rdpq_attach(&lastSavedFrame, NULL);
415 rdpq_set_mode_copy(false);
416 rdpq_tex_blit(disp, 0, 0, NULL);
417 rdpq_detach();
418
419 //mutex_lock(&mutex[0]);
420
422 successfulSave = true;
423
424 //mutex_unlock(&mutex[0]);
425
426 }
427
428 if (held.d_left) {
429 rdpq_set_mode_copy(true);
430 rdpq_tex_blit(&lastSavedFrame, 0, 0, NULL);
431 }
432
433 if (pressed.b) {
434 if (!sdCardExists) {
435 //mutex_lock(&mutex);
436
437 scrType = SCREENSHOT_NO_SD_CARD;
438 bytesWritten = 0;
439 encodedTime = 0.0f;
440 successfulSave = false;
441
442 //mutex_unlock(&mutex);
443 }
444 else {
445 surface_t img = surface_alloc(FMT_RGBA16, 320, 240);
446
447 float startEncode, endEncode;
448 //bool successfulScreenshot = false;
449
450 rdpq_attach(&img, NULL);
451 rdpq_set_mode_copy(false);
452 rdpq_tex_blit(disp, 0, 0, NULL);
453 rdpq_detach();
454
455 startEncode = timer_ticks();
456
457 //thrd_create(&thread, (thrd_start_t)save_screenshot_raw, &img, "sd:/screenshot.raw", &bytesWritten);
458 successfulSave = save_screenshot_raw(&img, "sd:/screenshot.raw", &bytesWritten);
459 //thrd_join(thread, &successfulScreenshot);
460
461 endEncode = timer_ticks();
462
463 //mutex_lock(&mutex);
464
465 //successfulSave = successfulScreenshot;
466
467 encodedTime = TIMER_MICROS(endEncode - startEncode) / 1000.0f; // Convert to milliseconds
468
469 scrType = SCREENSHOT_TYPE_RAW;
470
472
473 surface_free(&img);
474 }
475
476
477 }
478 else if (pressed.a) {
479
480 if (!sdCardExists) {
481 //mutex_lock(&mutex);
482
483 scrType = SCREENSHOT_NO_SD_CARD;
484 bytesWritten = 0;
485 encodedTime = 0.0f;
486 successfulSave = false;
487
488 //mutex_unlock(&mutex);
489 }
490 else {
491 surface_t img = surface_alloc(FMT_RGBA16, 320, 240);
492
493 float startEncode, endEncode;
494 //bool successfulScreenshot = false;
495
496 rdpq_attach(&img, NULL);
497 rdpq_set_mode_copy(false);
498 rdpq_tex_blit(disp, 0, 0, NULL);
499 rdpq_detach();
500
501 startEncode = timer_ticks();
502
503 //thrd_create(&thread, (thrd_start_t)save_screenshot, &img, "sd:/screenshot.qoi", &bytesWritten);
504 successfulSave = save_screenshot(&img, "sd:/screenshot.qoi", &bytesWritten);
505 //thrd_join(thread, &successfulScreenshot);
506
507 endEncode = timer_ticks();
508
509 //mutex_lock(&mutex);
510
511 //successfulSave = successfulScreenshot;
512
513 encodedTime = TIMER_MICROS(endEncode - startEncode) / 1000.0f; // Convert to milliseconds
514
515 scrType = SCREENSHOT_TYPE_QOI;
516
517 //mutex_unlock(&mutex);
518
519 surface_free(&img);
520 }
521 }
522 else if (pressed.d_up) {
523 surface_t img = surface_alloc(FMT_RGBA16, 320, 240);
524
525 float startEncode, endEncode;
526 //bool successfulScreenshot = false;
527
528 rdpq_attach(&img, NULL);
529 rdpq_set_mode_copy(false);
530 rdpq_tex_blit(disp, 0, 0, NULL);
531 rdpq_detach();
532
533 startEncode = timer_ticks();
534 //thrd_create(&thread, (thrd_start_t)save_screenshot_null, &img, &bytesWritten);
535 successfulSave = save_screenshot_null(&img, &bytesWritten);
536 endEncode = timer_ticks();
537 //thrd_join(thread, &successfulScreenshot);
538
539 //mutex_lock(&mutex);
540
541 //successfulSave = successfulScreenshot;
542
543 encodedTime = TIMER_MICROS(endEncode - startEncode) / 1000.0f; // Convert to milliseconds
544
545 scrType = SCREENSHOT_TYPE_NULL;
546
547 //mutex_unlock(&mutex);
548
549 surface_free(&img);
550
551 }
552
553 rdpq_detach_show();
554
555 }
556
557 return 0;
558}
Color conversion utilities for N64 QOI encoder.
uint32_t n64_color16_to_rgba32(uint16_t px16)
Convert N64 16-bit (5R-5G-5B-1A) to 0xRRGGBBAA (32-bit).
Definition colorconv.h:56
uint16_t read_be_u16(uint16_t val)
Reads an 16-bit unsigned big endian integer number.
Definition colorconv.h:47
Configuration constants for N64 QOI encoder.
#define QOI_ENC_REVISION_DATE
Revision date of the QOI encoder for N64.
Definition config.h:45
#define QOI_ENC_VERSION
Version of the QOI encoder for N64.
Definition config.h:43
int main(void)
Entry point for this N64 ROM.
Definition main.c:232
bool save_screenshot_null(surface_t *disp, uint32_t *bytesWritten)
Saves the screenshot to null, which is used for testing the performance of the encoder without the bo...
Definition main.c:146
bool save_screenshot_raw(surface_t *disp, const char *filename, uint32_t *bytesWritten)
Saves the raw screenshot to the SD card if it somehow exists.
Definition main.c:207
#define ENC_BUFFER_SIZE
Small buffer size for encoder because of memory constrant of 4MB for N64 without the 4MB Expansion Pa...
Definition main.c:49
whichScreenshotType
Checks what type of screenshot was taken to perform different actions based on the type of screenshot...
Definition main.c:52
@ SCREENSHOT_TYPE_RAW
The screenshot was saved as a raw file, which is used for testing the performance of writing to the S...
Definition main.c:58
@ SCREENSHOT_TYPE_NULL
The screenshot was saved to null, which is used for testing the performance of the encoder without th...
Definition main.c:56
@ SCREENSHOT_NO_SD_CARD
The screenshot was not saved because the SD card doesn't exist, which is used for testing the perform...
Definition main.c:64
@ SCREENSHOT_NEVER_TAKEN
No screenshot has been taken yet.
Definition main.c:54
@ SCREENSHOT_TYPE_QOI
The screenshot was saved as a QOI file, which is the main functionality of this project.
Definition main.c:60
@ SCREENSHOT_FRAME_CAPTURED
The screenshot was captured into a buffer but not saved, which is used for testing the performance of...
Definition main.c:62
bool save_screenshot(surface_t *disp, const char *filename, uint32_t *bytesWritten)
Saves the screenshot to the SD card if it somehow exists.
Definition main.c:75
QOI Encoder Library for N64.
void write_qoi_header(qoi_desc_t *desc, void *dest)
Writes the QOI metadata information to the file.
void qoi_set_dimensions(qoi_desc_t *desc, uint32_t width, uint32_t height)
Sets the image dimensions of an image for QOI descriptor.
static const uint8_t QOI_PADDING[8]
QOI end of file padding bytes.
Definition qoi_enc_n64.h:91
void qoi_encode_chunk(qoi_desc_t *desc, qoi_enc_t *enc, void *qoi_pixel_bytes)
Encode pixel data into QOI opcodes.
bool qoi_enc_init(qoi_desc_t *desc, qoi_enc_t *enc)
Initalize the QOI encoder to the default state.
void qoi_set_channels(qoi_desc_t *desc, uint8_t channels)
Sets the amount of channels of an image for QOI descriptor.
@ QOI_SRGB
Definition qoi_enc_n64.h:85
bool qoi_enc_set_buffer(qoi_enc_t *enc, void *newBuffer, uint32_t len, bool shouldFreePrevBuffer)
Sets a buffer for the QOI encoder, automatically free if free buffer flag is set.
bool qoi_enc_reset_buffer(qoi_enc_t *enc)
Resets the buffer of the QOI encoder to the default state.
void qoi_set_colorspace(qoi_desc_t *desc, uint8_t colorspace)
Sets the colorspace of an image for QOI descriptor.
QOI descriptor as read by the header of a QOI file.
Definition qoi_enc_n64.h:95
QOI encoder structure.
uint32_t buffer_offset
The offset of the buffer to write the next encoded data to.
uint8_t * enc_buffer
The buffer to store the encoded QOI data before writing to the file.
uint32_t buffer_len
The total length of the buffer.
uint32_t len
The total length of the pixel data to encode (width * height).
uint32_t pixels_written
The total number of pixels encoded so far.