Skip to content

Commit

Permalink
Limit fps choices to 30fps and 60fps. If 60fps is selected, no chroma…
Browse files Browse the repository at this point in the history
… channel is used in-game. If 30fps is selected, the higher compatibility with chroma is used and only every second frame is transfered.
  • Loading branch information
Staacks committed Jul 8, 2023
1 parent f2467bb commit 39937f4
Show file tree
Hide file tree
Showing 12 changed files with 1,212 additions and 264 deletions.
913 changes: 913 additions & 0 deletions firmware/jpeg/base_jpeg_no_chroma.h

Large diffs are not rendered by default.

Binary file added firmware/jpeg/base_no_chroma.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
89 changes: 63 additions & 26 deletions firmware/jpeg/generateBaseJpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ def generateAPP0():
data.extend([0x00, 0x00]) #Thumbnail 0x0
return data

def generateAPP0NoChroma():
data = bytearray([0xff, 0xe0])
data.extend([0x00, 0x11]) #length 17
data.extend(b"JFIF\x00") #JFIF
data.extend([0x01, 0x01]) #Version 1.1
data.extend([0x01]) #Units DPI
data.extend([0x00, 0x48, 0x00, 0x48]) #Density 72x72
data.extend([0x00, 0x00]) #Thumbnail 0x0
data.extend([0x00]) #No meaning, we just need to pad it, so that the data block aligns with a multiple of 32bit
return data

def generateDQT(destination):
data = bytearray([0xff, 0xdb])
data.extend([0x00, 0x43]) #Length 67
Expand All @@ -34,6 +45,16 @@ def generateSOF():
data.extend([0x03, 0x11, 0x00]) #Chrominance Cr channel setup
return data

def generateSOFNoChroma():
data = bytearray([0xff, 0xc0])
data.extend([0x00, 0x0b]) #Length 11
data.extend([0x08]) #Precision
data.extend([0x04, 0x80]) #height 8*144
data.extend([0x05, 0x00]) #width 8*160
data.extend([0x01]) #Components
data.extend([0x01, 0x22, 0x00]) #Luminance channel setup
return data

def generateDHT_DC():
data = bytearray([0xff, 0xc4])
data.extend([0x00, 0x17]) #Length 23
Expand Down Expand Up @@ -121,30 +142,46 @@ def generateData_chrominance():
def generateEOI():
return bytearray([0xff, 0xd9])

data = bytearray()
data.extend(generateSOI())
data.extend(generateAPP0())
data.extend(generateDQT(0x00)) #DQT
data.extend(generateSOF())
data.extend(generateDHT_DC())
data.extend(generateDHT_DC_chrominance())
data.extend(generateDHT_AC())
data.extend(generateSOS())
data.extend(generateData())
data.extend(generateSOS_chrominance())
data.extend(generateData_chrominance())
data.extend(generateEOI())



with open("base.jpg", "wb") as f:
f.write(data)

with open("base_jpeg.h", "w") as f:
f.write("unsigned char __in_flash(\"jpeg\") base_jpeg[] = {")
for i in range(len(data)):
if i % 16 == 0:
f.write("\n")
f.write(' 0x{:02x},'.format(data[i]))
f.write("\n};\n")
def baseJpeg():
data = bytearray()
data.extend(generateSOI())
data.extend(generateAPP0())
data.extend(generateDQT(0x00)) #DQT
data.extend(generateSOF())
data.extend(generateDHT_DC())
data.extend(generateDHT_DC_chrominance())
data.extend(generateDHT_AC())
data.extend(generateSOS())
data.extend(generateData())
data.extend(generateSOS_chrominance())
data.extend(generateData_chrominance())
data.extend(generateEOI())
return data

def baseJpegNoChroma():
data = bytearray()
data.extend(generateSOI())
data.extend(generateAPP0NoChroma())
data.extend(generateDQT(0x00)) #DQT
data.extend(generateSOFNoChroma())
data.extend(generateDHT_DC())
data.extend(generateDHT_AC())
data.extend(generateSOS())
data.extend(generateData())
data.extend(generateEOI())
return data

def generateFiles(jpg, h, var, data):
with open(jpg, "wb") as f:
f.write(data)

with open(h, "w") as f:
f.write("unsigned char __in_flash(\"jpeg\") " + var + "[] = {")
for i in range(len(data)):
if i % 16 == 0:
f.write("\n")
f.write(' 0x{:02x},'.format(data[i]))
f.write("\n};\n")

generateFiles("base.jpg", "base_jpeg.h", "base_jpeg", baseJpeg())
generateFiles("base_no_chroma.jpg", "base_jpeg_no_chroma.h", "base_jpeg_no_chroma", baseJpegNoChroma())
5 changes: 5 additions & 0 deletions firmware/jpeg/jpeg.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@

#define JPEG_DATA_SIZE (SCREEN_SIZE * 5 / 8) //5bit per pixel, see https://github.com/Staacks/gbinterceptor/issues/17
#define JPEG_HEADER_SIZE 188
#define JPEG_HEADER_SIZE_NO_CHROMA 160
#define JPEG_END_SIZE 2895
#define JPEG_END_SIZE_NO_CHROMA 2
#define FRAME_SIZE (JPEG_DATA_SIZE + JPEG_HEADER_SIZE + JPEG_END_SIZE)
#define FRAME_SIZE_NO_CHROMA (JPEG_DATA_SIZE + JPEG_HEADER_SIZE_NO_CHROMA + JPEG_END_SIZE_NO_CHROMA)

#define JPEG_CHROMA_OFFSET (JPEG_HEADER_SIZE + JPEG_DATA_SIZE + 12)

void prepareJpegEncoding();
Expand Down
57 changes: 38 additions & 19 deletions firmware/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "gamedb/game_detection.h"

#include "jpeg/base_jpeg.h"
#include "jpeg/base_jpeg_no_chroma.h"

#include "screens/default.h"
#include "screens/off.h"
Expand All @@ -35,12 +36,15 @@ dma_channel_config dmaConfig;

bool dmgColorMode = false;

bool includeChroma = true;
bool is30fpsFrame = true;

void setupGPIO() {
gpio_init(GBSENSE_PIN);
gpio_init(LED_SWITCH_PIN);
gpio_set_dir(GBSENSE_PIN, GPIO_IN);

//IMPORTANT: Since the mode button directly connects this GPIO pin to ground, we only want to drive it to the ground state to enable the LED. Disabling the LED is done by disabling the output, never by driving actively to HIGH as this might cause a short circuit if the button is pressed at the same time.
//IMPORTANT: Since the mode button directly connects this GPIO pin to ground, we only want to drive it to the ground state to enable the LED. Disabling the LED is done by disabling the output, never by driving actively to HIGH as this might cause a short circuit if the button is pressed at the same time.
gpio_put(LED_SWITCH_PIN, 0);
gpio_set_dir(LED_SWITCH_PIN, GPIO_IN);
gpio_pull_up(LED_SWITCH_PIN);
Expand Down Expand Up @@ -97,8 +101,11 @@ bool usbSendFrame() {
if (tud_video_n_streaming(0, 0)) {
if (!frameSending) {
if (swapFrontbuffer()) {
frameSending = true;
tud_video_n_frame_xfer(0, 0, (void*)frontBuffer, FRAME_SIZE);
is30fpsFrame = !is30fpsFrame; //If clock is determined by the Game Boy and if we include Chroma, we only send every second frame (i.e. 30fps)
if (is30fpsFrame || !includeChroma || !running) {
frameSending = true;
tud_video_n_frame_xfer(0, 0, (void*)(frontBuffer + (includeChroma || !running ? 0 : JPEG_HEADER_SIZE - JPEG_HEADER_SIZE_NO_CHROMA)), includeChroma || !running ? FRAME_SIZE : FRAME_SIZE_NO_CHROMA);
}
return true;
}
}
Expand All @@ -123,15 +130,20 @@ void updateFallbackScreen() {
}
}

void fillBufferWithBaseJpeg(uint8_t * target, int dmaChannel, dma_channel_config dmaConfig) {
dma_channel_configure(dmaChannel, &dmaConfig, target, base_jpeg, FRAME_SIZE / 4, true);
//We are using faster (?) 32 bit copies, but the JPEG might not have a size that is a multiple of 4 byte, so let's just copy the remaining ones manually.
for (int i = 4 * (FRAME_SIZE / 4); i < FRAME_SIZE; i++)
target[i] = base_jpeg[i];
//Wait for DMA to finish
while (dma_channel_is_busy(dmaChannel)) {
tud_task();
}
void fillBufferWithBaseJpeg(uint8_t * target, bool includeChroma) {
int chromaOffset = (includeChroma ? 0 : JPEG_HEADER_SIZE - JPEG_HEADER_SIZE_NO_CHROMA);
int headerSize = (includeChroma ? JPEG_HEADER_SIZE : JPEG_HEADER_SIZE_NO_CHROMA);
for (int i = 0; i < headerSize; i++)
target[i + chromaOffset] = (includeChroma ? base_jpeg : base_jpeg_no_chroma)[i];
int fullSize = (includeChroma ? FRAME_SIZE : FRAME_SIZE_NO_CHROMA);
for (int i = JPEG_DATA_SIZE + headerSize; i < fullSize; i++)
target[i + chromaOffset] = (includeChroma ? base_jpeg : base_jpeg_no_chroma)[i];
}

void updateIncludeChroma() {
fillBufferWithBaseJpeg((uint8_t *)frontBuffer, includeChroma || !running);
fillBufferWithBaseJpeg((uint8_t *)readyBuffer, includeChroma || !running);
fallbackScreenType = FST_NONE;
}

int main(void) {
Expand All @@ -143,26 +155,30 @@ int main(void) {
tud_init(BOARD_TUD_RHPORT);
stdio_init_all();
setupGPIO();
fillBufferWithBaseJpeg((uint8_t *)frontBuffer, dmaChannel, dmaConfig);
fillBufferWithBaseJpeg((uint8_t *)readyBuffer, dmaChannel, dmaConfig);
prepareJpegEncoding();

multicore_launch_core1(handleMemoryBus);

while (1) {

printf("Waiting for game.\n");
updateIncludeChroma();
while (!running) {
if (isGameBoyOn()) {
if (fallbackScreenType == FST_NONE || fallbackScreenType == FST_OFF) {
loadFallbackScreen(default_raw, FST_DEFAULT);
renderText("Waiting for game...", 0x03, 0x00, (uint8_t *)backBuffer, 5, 79);
if (!includeChroma)
renderText(" 60 fps mode.\nSwitch to 30fps if\nthere are problems.", 0x03, 0x00, (uint8_t *)backBuffer, 5, 100);
readyBufferIsNew = false;
startBackbufferToJPEG(false);
}
} else {
if (fallbackScreenType == FST_NONE || fallbackScreenType == FST_DEFAULT || fallbackScreenType == FST_ERROR) {
loadFallbackScreen(off_raw, FST_OFF);
renderText("The Game Boy\nis turned off", 0x03, 0x00, (uint8_t *)backBuffer, 40, 79);
if (!includeChroma)
renderText(" 60 fps mode.\nSwitch to 30fps if\nthere are problems.", 0x03, 0x00, (uint8_t *)backBuffer, 5, 100);
readyBufferIsNew = false;
startBackbufferToJPEG(false);
}
Expand All @@ -179,6 +195,7 @@ int main(void) {

ledOn();
printf("Game started. Cycle ratio: %d\n", cycleRatio);
updateIncludeChroma();
ppuInit();

uint lastCycle = cycleIndex;
Expand Down Expand Up @@ -272,21 +289,23 @@ void tud_umount_cb(void) {
// remote_wakeup_en : if host allow us to perform remote wakeup
// Within 7ms, device must draw an average of current less than 2.5 mA from bus
void tud_suspend_cb(bool remote_wakeup_en) {
(void) remote_wakeup_en;
(void) remote_wakeup_en;
}

// Invoked when usb bus is resumed
void tud_resume_cb(void) {
}

void tud_video_frame_xfer_complete_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx) {
(void)ctl_idx; (void)stm_idx;
frameSending = false;
(void)ctl_idx; (void)stm_idx;
frameSending = false;
}

int tud_video_commit_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx, video_probe_and_commit_control_t const *parameters) {
(void)ctl_idx; (void)stm_idx;
return VIDEO_ERROR_NONE;
(void)ctl_idx; (void)stm_idx;
includeChroma = parameters->dwFrameInterval > 200000 /*slower than 50fps*/;
updateIncludeChroma();
return VIDEO_ERROR_NONE;
}

void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts) {
Expand Down
2 changes: 1 addition & 1 deletion firmware/main.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#define LED_SWITCH_PIN 1
#define LED_PIN_MASK 0x02

#define VERSION "1.2.0-beta2"
#define VERSION "1.2.0-beta3"

//On-screen display
#define MODE_INFO_DURATION 100 //Duration of the mode info in frames
Expand Down
1 change: 0 additions & 1 deletion firmware/ppu.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
#define SCREEN_W 160
#define SCREEN_H 144
#define SCREEN_SIZE (SCREEN_W * SCREEN_H)
#define FRAME_SIZE (JPEG_DATA_SIZE + JPEG_HEADER_SIZE + JPEG_END_SIZE) //Header, Huffman tables, quantization tables etc.

#define CYCLES_PER_FRAME 17556
#define CYCLES_PER_LINE 114
Expand Down
Loading

0 comments on commit 39937f4

Please sign in to comment.