From 634a54c04665207ac9c40fcc382d2a3711ffd029 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 15 Nov 2018 00:21:21 +0200 Subject: [PATCH] SGB resolution support (Cocoa only so far) --- Cocoa/Document.m | 18 ++++++++----- Cocoa/GBGLShader.h | 2 +- Cocoa/GBGLShader.m | 8 +++--- Cocoa/GBOpenGLView.m | 17 ++++--------- Cocoa/GBView.h | 1 + Cocoa/GBView.m | 31 ++++++++++++++++++----- Cocoa/GBViewMetal.m | 52 +++++++++++++++++++++++--------------- Core/display.c | 35 ++++++++++++++++++++----- Core/gb.c | 25 ++++++++++++++++++ Core/gb.h | 8 +++++- Core/sgb.c | 26 +++++++++++++++++++ Core/sgb.h | 1 + Shaders/MasterShader.fsh | 3 ++- Shaders/MasterShader.metal | 3 ++- 14 files changed, 170 insertions(+), 60 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 05b3b45..24e99a3 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -225,8 +225,9 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, - (void) run { running = true; - GB_set_pixels_output(&gb, self.view.pixels); self.view.gb = &gb; + [self.view screenSizeChanged]; + GB_set_pixels_output(&gb, self.view.pixels); GB_set_sample_rate(&gb, 96000); self.audioClient = [[GBAudioClient alloc] initWithRendererBlock:^(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer) { GB_apu_copy_buffer(&gb, buffer, nFrames); @@ -565,21 +566,24 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, if (fullScreen) { return newFrame; } + size_t width = GB_get_screen_width(&gb), + height = GB_get_screen_height(&gb); + NSRect rect = window.contentView.frame; int titlebarSize = window.contentView.superview.frame.size.height - rect.size.height; - int step = 160 / [[window screen] backingScaleFactor]; + int step = width / [[window screen] backingScaleFactor]; rect.size.width = floor(rect.size.width / step) * step + step; - rect.size.height = rect.size.width / 10 * 9 + titlebarSize; + rect.size.height = rect.size.width * height / width + titlebarSize; if (rect.size.width > newFrame.size.width) { - rect.size.width = 160; - rect.size.height = 144 + titlebarSize; + rect.size.width = width; + rect.size.height = height + titlebarSize; } else if (rect.size.height > newFrame.size.height) { - rect.size.width = 160; - rect.size.height = 144 + titlebarSize; + rect.size.width = width; + rect.size.height = height + titlebarSize; } rect.origin = window.frame.origin; diff --git a/Cocoa/GBGLShader.h b/Cocoa/GBGLShader.h index 76291fb..1a12617 100644 --- a/Cocoa/GBGLShader.h +++ b/Cocoa/GBGLShader.h @@ -2,5 +2,5 @@ @interface GBGLShader : NSObject - (instancetype)initWithName:(NSString *) shaderName; -- (void) renderBitmap: (void *)bitmap previous:(void*) previous inSize:(NSSize)size scale: (double) scale; +- (void) renderBitmap: (void *)bitmap previous:(void*) previous sized:(NSSize)srcSize inSize:(NSSize)dstSize scale: (double) scale; @end diff --git a/Cocoa/GBGLShader.m b/Cocoa/GBGLShader.m index 444ee89..fe636f8 100644 --- a/Cocoa/GBGLShader.m +++ b/Cocoa/GBGLShader.m @@ -79,19 +79,19 @@ void main(void) {\n\ return self; } -- (void) renderBitmap: (void *)bitmap previous:(void*) previous inSize:(NSSize)size scale: (double) scale +- (void) renderBitmap: (void *)bitmap previous:(void*) previous sized:(NSSize)srcSize inSize:(NSSize)dstSize scale: (double) scale { glUseProgram(program); - glUniform2f(resolution_uniform, size.width * scale, size.height * scale); + glUniform2f(resolution_uniform, dstSize.width * scale, dstSize.height * scale); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 160, 144, 0, GL_RGBA, GL_UNSIGNED_BYTE, bitmap); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, srcSize.width, srcSize.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, bitmap); glUniform1i(texture_uniform, 0); glUniform1i(mix_previous_uniform, previous != NULL); if (previous) { glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, previous_texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 160, 144, 0, GL_RGBA, GL_UNSIGNED_BYTE, previous); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, srcSize.width, srcSize.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, previous); glUniform1i(previous_texture_uniform, 1); } glBindFragDataLocation(program, 0, "frag_color"); diff --git a/Cocoa/GBOpenGLView.m b/Cocoa/GBOpenGLView.m index 505f5db..67a9f8d 100644 --- a/Cocoa/GBOpenGLView.m +++ b/Cocoa/GBOpenGLView.m @@ -13,18 +13,11 @@ double scale = self.window.backingScaleFactor; glViewport(0, 0, self.bounds.size.width * scale, self.bounds.size.height * scale); - if (gbview.shouldBlendFrameWithPrevious) { - [self.shader renderBitmap:gbview.currentBuffer - previous:gbview.previousBuffer - inSize:self.bounds.size - scale:scale]; - } - else { - [self.shader renderBitmap:gbview.currentBuffer - previous:NULL - inSize:self.bounds.size - scale:scale]; - } + [self.shader renderBitmap:gbview.currentBuffer + previous:gbview.shouldBlendFrameWithPrevious? gbview.previousBuffer : NULL + sized:NSMakeSize(GB_get_screen_width(gbview.gb), GB_get_screen_height(gbview.gb)) + inSize:self.bounds.size + scale:scale]; glFlush(); } diff --git a/Cocoa/GBView.h b/Cocoa/GBView.h index c054e09..f4c5e44 100644 --- a/Cocoa/GBView.h +++ b/Cocoa/GBView.h @@ -13,4 +13,5 @@ - (void) createInternalView; - (uint32_t *)currentBuffer; - (uint32_t *)previousBuffer; +- (void)screenSizeChanged; @end diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index a2dbe72..debac87 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -44,9 +44,8 @@ - (void) _init { - image_buffers[0] = malloc(160 * 144 * 4); - image_buffers[1] = malloc(160 * 144 * 4); - image_buffers[2] = malloc(160 * 144 * 4); + [self screenSizeChanged]; + _shouldBlendFrameWithPrevious = 1; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ratioKeepingChanged) name:@"GBAspectChanged" object:nil]; tracking_area = [ [NSTrackingArea alloc] initWithRect:(NSRect){} @@ -60,6 +59,24 @@ self.internalView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; } +- (void)screenSizeChanged +{ + if (!_gb) return; + if (image_buffers[0]) free(image_buffers[0]); + if (image_buffers[1]) free(image_buffers[1]); + if (image_buffers[2]) free(image_buffers[2]); + + size_t buffer_size = sizeof(image_buffers[0][0]) * GB_get_screen_width(_gb) * GB_get_screen_height(_gb); + + image_buffers[0] = malloc(buffer_size); + image_buffers[1] = malloc(buffer_size); + image_buffers[2] = malloc(buffer_size); + + dispatch_async(dispatch_get_main_queue(), ^{ + [self setFrame:self.superview.frame]; + }); +} + - (void) ratioKeepingChanged { [self setFrame:self.superview.frame]; @@ -112,14 +129,16 @@ frame = self.superview.frame; if (![[NSUserDefaults standardUserDefaults] boolForKey:@"GBAspectRatioUnkept"]) { double ratio = frame.size.width / frame.size.height; - if (ratio >= 160.0/144.0) { - double new_width = round(frame.size.height / 144.0 * 160.0); + double width = GB_get_screen_width(_gb); + double height = GB_get_screen_height(_gb); + if (ratio >= width / height) { + double new_width = round(frame.size.height / height * width); frame.origin.x = floor((frame.size.width - new_width) / 2); frame.size.width = new_width; frame.origin.y = 0; } else { - double new_height = round(frame.size.width / 160.0 * 144.0); + double new_height = round(frame.size.width / width * height); frame.origin.y = floor((frame.size.height - new_height) / 2); frame.size.height = new_height; frame.origin.x = 0; diff --git a/Cocoa/GBViewMetal.m b/Cocoa/GBViewMetal.m index 4867d51..124db77 100644 --- a/Cocoa/GBViewMetal.m +++ b/Cocoa/GBViewMetal.m @@ -1,14 +1,5 @@ #import "GBViewMetal.h" -#define WIDTH 160 -#define HEIGHT 144 -#define PITCH (160 * 4) - -static const MTLRegion region = { - {0, 0, 0}, // MTLOrigin - {WIDTH, HEIGHT, 1} // MTLSize -}; - static const vector_float2 rect[] = { {-1, -1}, @@ -37,6 +28,29 @@ static const vector_float2 rect[] = return false; } +- (void) allocateTextures +{ + if (!device) return; + if (!self.gb) return; + + MTLTextureDescriptor *texture_descriptor = [[MTLTextureDescriptor alloc] init]; + + texture_descriptor.pixelFormat = MTLPixelFormatRGBA8Unorm; + + texture_descriptor.width = GB_get_screen_width(self.gb); + texture_descriptor.height = GB_get_screen_height(self.gb); + + texture = [device newTextureWithDescriptor:texture_descriptor]; + previous_texture = [device newTextureWithDescriptor:texture_descriptor]; + +} + +- (void)screenSizeChanged +{ + [super screenSizeChanged]; + [self allocateTextures]; +} + - (void)createInternalView { MTKView *view = [[MTKView alloc] initWithFrame:self.frame device:(device = MTLCreateSystemDefaultDevice())]; @@ -44,15 +58,7 @@ static const vector_float2 rect[] = self.internalView = view; view.paused = YES; - MTLTextureDescriptor *texture_descriptor = [[MTLTextureDescriptor alloc] init]; - - texture_descriptor.pixelFormat = MTLPixelFormatRGBA8Unorm; - - texture_descriptor.width = WIDTH; - texture_descriptor.height = HEIGHT; - - texture = [device newTextureWithDescriptor:texture_descriptor]; - previous_texture = [device newTextureWithDescriptor:texture_descriptor]; + [self allocateTextures]; vertices = [device newBufferWithBytes:rect length:sizeof(rect) @@ -134,15 +140,21 @@ static const vector_float2 rect[] = - (void)drawInMTKView:(nonnull MTKView *)view { if (!(view.window.occlusionState & NSWindowOcclusionStateVisible)) return; + + MTLRegion region = { + {0, 0, 0}, // MTLOrigin + {texture.width, texture.height, 1} // MTLSize + }; + [texture replaceRegion:region mipmapLevel:0 withBytes:[self currentBuffer] - bytesPerRow:PITCH]; + bytesPerRow:texture.width * 4]; if ([self shouldBlendFrameWithPrevious]) { [previous_texture replaceRegion:region mipmapLevel:0 withBytes:[self previousBuffer] - bytesPerRow:PITCH]; + bytesPerRow:texture.width * 4]; } MTLRenderPassDescriptor *render_pass_descriptor = view.currentRenderPassDescriptor; diff --git a/Core/display.c b/Core/display.c index 2759146..9c6e9cb 100644 --- a/Core/display.c +++ b/Core/display.c @@ -132,14 +132,25 @@ static void display_vblank(GB_gameboy_t *gb) if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || gb->stopped) || gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON)) { /* LCD is off, set screen to white or black (if LCD is on in stop mode) */ - uint32_t color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped ? - gb->rgb_encode_callback(gb, 0, 0, 0) : - gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); - for (unsigned i = 0; i < WIDTH * LINES; i++) { - gb ->screen[i] = color; + if (gb->sgb_screen_buffer) { + uint8_t color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped ? 0 : 0xFF; + for (unsigned i = 0; i < WIDTH * LINES; i++) { + gb ->sgb_screen_buffer[i] = color; + } + } + else { + uint32_t color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped ? + gb->rgb_encode_callback(gb, 0, 0, 0) : + gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); + for (unsigned i = 0; i < WIDTH * LINES; i++) { + gb ->screen[i] = color; + } } } + if (GB_is_sgb(gb)) { + GB_sgb_render(gb); + } gb->vblank_callback(gb); GB_timing_sync(gb); } @@ -375,7 +386,12 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) if (!gb->cgb_mode) { pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3); } - gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->background_palettes_rgb[fifo_item->palette * 4 + pixel]; + if (gb->sgb_screen_buffer) { + gb->sgb_screen_buffer[gb->position_in_line + gb->current_line * WIDTH] = pixel; + } + else { + gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->background_palettes_rgb[fifo_item->palette * 4 + pixel]; + } } if (draw_oam) { @@ -384,7 +400,12 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) /* Todo: Verify access timings */ pixel = ((gb->io_registers[oam_fifo_item->palette? GB_IO_OBP1 : GB_IO_OBP0] >> (pixel << 1)) & 3); } - gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->sprite_palettes_rgb[oam_fifo_item->palette * 4 + pixel]; + if (gb->sgb_screen_buffer) { + gb->sgb_screen_buffer[gb->position_in_line + gb->current_line * WIDTH] =pixel; + } + else { + gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->sprite_palettes_rgb[oam_fifo_item->palette * 4 + pixel]; + } } gb->position_in_line++; diff --git a/Core/gb.c b/Core/gb.c index 401eba8..29d6df0 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -139,6 +139,9 @@ void GB_free(GB_gameboy_t *gb) if (gb->breakpoints) { free(gb->breakpoints); } + if (gb->sgb_screen_buffer) { + free(gb->sgb_screen_buffer); + } #ifndef DISABLE_DEBUGGER GB_debugger_clear_symbols(gb); #endif @@ -642,6 +645,18 @@ void GB_reset(GB_gameboy_t *gb) gb->sgb_player_count = 1; + if (GB_is_sgb(gb)) { + if (!gb->sgb_screen_buffer) { + gb->sgb_screen_buffer = malloc(160 * 144); + } + } + else { + if (gb->sgb_screen_buffer) { + free(gb->sgb_screen_buffer); + gb->sgb_screen_buffer = NULL; + } + } + /* Todo: Ugly, fixme, see comment in the timer state machine */ gb->div_state = 3; @@ -744,3 +759,13 @@ uint32_t GB_get_clock_rate(GB_gameboy_t *gb) } return CPU_FREQUENCY * gb->clock_multiplier; } + +size_t GB_get_screen_width(GB_gameboy_t *gb) +{ + return (gb && GB_is_sgb(gb))? 256 : 160; +} + +size_t GB_get_screen_height(GB_gameboy_t *gb) +{ + return (gb && GB_is_sgb(gb))? 224 : 144; +} diff --git a/Core/gb.h b/Core/gb.h index ec8c7e8..83f880c 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -475,6 +475,9 @@ struct GB_gameboy_internal_s { bool sgb_ready_for_write; bool sgb_ready_for_stop; bool sgb_disable_commands; + + /* Screen buffer */ + uint8_t *sgb_screen_buffer; /* Multiplayer Input */ uint8_t sgb_player_count, sgb_current_player; ); @@ -677,5 +680,8 @@ void GB_disconnect_serial(GB_gameboy_t *gb); uint32_t GB_get_clock_rate(GB_gameboy_t *gb); #endif void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier); - + +size_t GB_get_screen_width(GB_gameboy_t *gb); +size_t GB_get_screen_height(GB_gameboy_t *gb); + #endif /* GB_h */ diff --git a/Core/sgb.c b/Core/sgb.c index 755fea2..e3f71ad 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -127,3 +127,29 @@ void GB_sgb_write(GB_gameboy_t *gb, uint8_t value) break; } } + +void GB_sgb_render(GB_gameboy_t *gb) +{ + if (!gb->screen || !gb->rgb_encode_callback) return; + + uint32_t border = gb->rgb_encode_callback(gb, 0x9c, 0x9c, 0xa5); + uint32_t colors[] = { + gb->rgb_encode_callback(gb, 0xf7, 0xe7, 0xc6), + gb->rgb_encode_callback(gb, 0xd6, 0x8e, 0x49), + gb->rgb_encode_callback(gb, 0xa6, 0x37, 0x25), + gb->rgb_encode_callback(gb, 0x33, 0x1e, 0x50) + }; + + for (unsigned i = 0; i < 256 * 224; i++) { + gb->screen[i] = border; + } + + uint32_t *output = &gb->screen[48 + 39 * 256]; + uint8_t *input = gb->sgb_screen_buffer; + for (unsigned y = 0; y < 144; y++) { + for (unsigned x = 0; x < 160; x++) { + *(output++) = colors[*(input++) & 3]; + } + output += 256 - 160; + } +} diff --git a/Core/sgb.h b/Core/sgb.h index e3b52bd..c4541fa 100644 --- a/Core/sgb.h +++ b/Core/sgb.h @@ -4,6 +4,7 @@ #ifdef GB_INTERNAL void GB_sgb_write(GB_gameboy_t *gb, uint8_t value); +void GB_sgb_render(GB_gameboy_t *gb); #endif #endif diff --git a/Shaders/MasterShader.fsh b/Shaders/MasterShader.fsh index 55cdf49..a489cf7 100644 --- a/Shaders/MasterShader.fsh +++ b/Shaders/MasterShader.fsh @@ -21,7 +21,8 @@ void main() vec2 position = gl_FragCoord.xy - origin; position /= output_resolution; position.y = 1 - position.y; - + vec2 input_resolution = textureSize(image, 0); + if (mix_previous) { frag_color = mix(scale(image, position, input_resolution, output_resolution), scale(previous_image, position, input_resolution, output_resolution), 0.5); diff --git a/Shaders/MasterShader.metal b/Shaders/MasterShader.metal index 589c4ac..4cae3ae 100644 --- a/Shaders/MasterShader.metal +++ b/Shaders/MasterShader.metal @@ -3,7 +3,6 @@ #include using namespace metal; -constant float2 input_resolution = float2(160, 144); /* For GLSL compatibility */ typedef float2 vec2; @@ -49,6 +48,8 @@ fragment float4 fragment_shader(rasterizer_data in [[stage_in]], constant bool *mix_previous [[ buffer(0) ]], constant float2 *output_resolution [[ buffer(1) ]]) { + float2 input_resolution = float2(image.get_width(), image.get_height()); + in.texcoords.y = 1 - in.texcoords.y; if (*mix_previous) { return mix(scale(image, in.texcoords, input_resolution, *output_resolution),