SGB resolution support (Cocoa only so far)

This commit is contained in:
Lior Halphon 2018-11-15 00:21:21 +02:00
parent 6ba5cfbeef
commit 634a54c046
14 changed files with 170 additions and 60 deletions

View File

@ -225,8 +225,9 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
- (void) run - (void) run
{ {
running = true; running = true;
GB_set_pixels_output(&gb, self.view.pixels);
self.view.gb = &gb; self.view.gb = &gb;
[self.view screenSizeChanged];
GB_set_pixels_output(&gb, self.view.pixels);
GB_set_sample_rate(&gb, 96000); GB_set_sample_rate(&gb, 96000);
self.audioClient = [[GBAudioClient alloc] initWithRendererBlock:^(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer) { self.audioClient = [[GBAudioClient alloc] initWithRendererBlock:^(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer) {
GB_apu_copy_buffer(&gb, buffer, nFrames); 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) { if (fullScreen) {
return newFrame; return newFrame;
} }
size_t width = GB_get_screen_width(&gb),
height = GB_get_screen_height(&gb);
NSRect rect = window.contentView.frame; NSRect rect = window.contentView.frame;
int titlebarSize = window.contentView.superview.frame.size.height - rect.size.height; 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.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) { if (rect.size.width > newFrame.size.width) {
rect.size.width = 160; rect.size.width = width;
rect.size.height = 144 + titlebarSize; rect.size.height = height + titlebarSize;
} }
else if (rect.size.height > newFrame.size.height) { else if (rect.size.height > newFrame.size.height) {
rect.size.width = 160; rect.size.width = width;
rect.size.height = 144 + titlebarSize; rect.size.height = height + titlebarSize;
} }
rect.origin = window.frame.origin; rect.origin = window.frame.origin;

View File

@ -2,5 +2,5 @@
@interface GBGLShader : NSObject @interface GBGLShader : NSObject
- (instancetype)initWithName:(NSString *) shaderName; - (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 @end

View File

@ -79,19 +79,19 @@ void main(void) {\n\
return self; 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); glUseProgram(program);
glUniform2f(resolution_uniform, size.width * scale, size.height * scale); glUniform2f(resolution_uniform, dstSize.width * scale, dstSize.height * scale);
glActiveTexture(GL_TEXTURE0); glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture); 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(texture_uniform, 0);
glUniform1i(mix_previous_uniform, previous != NULL); glUniform1i(mix_previous_uniform, previous != NULL);
if (previous) { if (previous) {
glActiveTexture(GL_TEXTURE1); glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, previous_texture); 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); glUniform1i(previous_texture_uniform, 1);
} }
glBindFragDataLocation(program, 0, "frag_color"); glBindFragDataLocation(program, 0, "frag_color");

View File

@ -13,18 +13,11 @@
double scale = self.window.backingScaleFactor; double scale = self.window.backingScaleFactor;
glViewport(0, 0, self.bounds.size.width * scale, self.bounds.size.height * scale); glViewport(0, 0, self.bounds.size.width * scale, self.bounds.size.height * scale);
if (gbview.shouldBlendFrameWithPrevious) {
[self.shader renderBitmap:gbview.currentBuffer [self.shader renderBitmap:gbview.currentBuffer
previous:gbview.previousBuffer previous:gbview.shouldBlendFrameWithPrevious? gbview.previousBuffer : NULL
sized:NSMakeSize(GB_get_screen_width(gbview.gb), GB_get_screen_height(gbview.gb))
inSize:self.bounds.size inSize:self.bounds.size
scale:scale]; scale:scale];
}
else {
[self.shader renderBitmap:gbview.currentBuffer
previous:NULL
inSize:self.bounds.size
scale:scale];
}
glFlush(); glFlush();
} }

View File

@ -13,4 +13,5 @@
- (void) createInternalView; - (void) createInternalView;
- (uint32_t *)currentBuffer; - (uint32_t *)currentBuffer;
- (uint32_t *)previousBuffer; - (uint32_t *)previousBuffer;
- (void)screenSizeChanged;
@end @end

View File

@ -44,9 +44,8 @@
- (void) _init - (void) _init
{ {
image_buffers[0] = malloc(160 * 144 * 4); [self screenSizeChanged];
image_buffers[1] = malloc(160 * 144 * 4);
image_buffers[2] = malloc(160 * 144 * 4);
_shouldBlendFrameWithPrevious = 1; _shouldBlendFrameWithPrevious = 1;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ratioKeepingChanged) name:@"GBAspectChanged" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ratioKeepingChanged) name:@"GBAspectChanged" object:nil];
tracking_area = [ [NSTrackingArea alloc] initWithRect:(NSRect){} tracking_area = [ [NSTrackingArea alloc] initWithRect:(NSRect){}
@ -60,6 +59,24 @@
self.internalView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; 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 - (void) ratioKeepingChanged
{ {
[self setFrame:self.superview.frame]; [self setFrame:self.superview.frame];
@ -112,14 +129,16 @@
frame = self.superview.frame; frame = self.superview.frame;
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"GBAspectRatioUnkept"]) { if (![[NSUserDefaults standardUserDefaults] boolForKey:@"GBAspectRatioUnkept"]) {
double ratio = frame.size.width / frame.size.height; double ratio = frame.size.width / frame.size.height;
if (ratio >= 160.0/144.0) { double width = GB_get_screen_width(_gb);
double new_width = round(frame.size.height / 144.0 * 160.0); 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.origin.x = floor((frame.size.width - new_width) / 2);
frame.size.width = new_width; frame.size.width = new_width;
frame.origin.y = 0; frame.origin.y = 0;
} }
else { 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.origin.y = floor((frame.size.height - new_height) / 2);
frame.size.height = new_height; frame.size.height = new_height;
frame.origin.x = 0; frame.origin.x = 0;

View File

@ -1,14 +1,5 @@
#import "GBViewMetal.h" #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[] = static const vector_float2 rect[] =
{ {
{-1, -1}, {-1, -1},
@ -37,6 +28,29 @@ static const vector_float2 rect[] =
return false; 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 - (void)createInternalView
{ {
MTKView *view = [[MTKView alloc] initWithFrame:self.frame device:(device = MTLCreateSystemDefaultDevice())]; MTKView *view = [[MTKView alloc] initWithFrame:self.frame device:(device = MTLCreateSystemDefaultDevice())];
@ -44,15 +58,7 @@ static const vector_float2 rect[] =
self.internalView = view; self.internalView = view;
view.paused = YES; view.paused = YES;
MTLTextureDescriptor *texture_descriptor = [[MTLTextureDescriptor alloc] init]; [self allocateTextures];
texture_descriptor.pixelFormat = MTLPixelFormatRGBA8Unorm;
texture_descriptor.width = WIDTH;
texture_descriptor.height = HEIGHT;
texture = [device newTextureWithDescriptor:texture_descriptor];
previous_texture = [device newTextureWithDescriptor:texture_descriptor];
vertices = [device newBufferWithBytes:rect vertices = [device newBufferWithBytes:rect
length:sizeof(rect) length:sizeof(rect)
@ -134,15 +140,21 @@ static const vector_float2 rect[] =
- (void)drawInMTKView:(nonnull MTKView *)view - (void)drawInMTKView:(nonnull MTKView *)view
{ {
if (!(view.window.occlusionState & NSWindowOcclusionStateVisible)) return; if (!(view.window.occlusionState & NSWindowOcclusionStateVisible)) return;
MTLRegion region = {
{0, 0, 0}, // MTLOrigin
{texture.width, texture.height, 1} // MTLSize
};
[texture replaceRegion:region [texture replaceRegion:region
mipmapLevel:0 mipmapLevel:0
withBytes:[self currentBuffer] withBytes:[self currentBuffer]
bytesPerRow:PITCH]; bytesPerRow:texture.width * 4];
if ([self shouldBlendFrameWithPrevious]) { if ([self shouldBlendFrameWithPrevious]) {
[previous_texture replaceRegion:region [previous_texture replaceRegion:region
mipmapLevel:0 mipmapLevel:0
withBytes:[self previousBuffer] withBytes:[self previousBuffer]
bytesPerRow:PITCH]; bytesPerRow:texture.width * 4];
} }
MTLRenderPassDescriptor *render_pass_descriptor = view.currentRenderPassDescriptor; MTLRenderPassDescriptor *render_pass_descriptor = view.currentRenderPassDescriptor;

View File

@ -132,6 +132,13 @@ 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)) { 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) */ /* LCD is off, set screen to white or black (if LCD is on in stop mode) */
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 ? 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, 0, 0, 0) :
gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF);
@ -139,7 +146,11 @@ static void display_vblank(GB_gameboy_t *gb)
gb ->screen[i] = color; gb ->screen[i] = color;
} }
} }
}
if (GB_is_sgb(gb)) {
GB_sgb_render(gb);
}
gb->vblank_callback(gb); gb->vblank_callback(gb);
GB_timing_sync(gb); GB_timing_sync(gb);
} }
@ -375,8 +386,13 @@ static void render_pixel_if_possible(GB_gameboy_t *gb)
if (!gb->cgb_mode) { if (!gb->cgb_mode) {
pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3); pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3);
} }
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]; gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->background_palettes_rgb[fifo_item->palette * 4 + pixel];
} }
}
if (draw_oam) { if (draw_oam) {
uint8_t pixel = oam_fifo_item->pixel; uint8_t pixel = oam_fifo_item->pixel;
@ -384,8 +400,13 @@ static void render_pixel_if_possible(GB_gameboy_t *gb)
/* Todo: Verify access timings */ /* Todo: Verify access timings */
pixel = ((gb->io_registers[oam_fifo_item->palette? GB_IO_OBP1 : GB_IO_OBP0] >> (pixel << 1)) & 3); pixel = ((gb->io_registers[oam_fifo_item->palette? GB_IO_OBP1 : GB_IO_OBP0] >> (pixel << 1)) & 3);
} }
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->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->sprite_palettes_rgb[oam_fifo_item->palette * 4 + pixel];
} }
}
gb->position_in_line++; gb->position_in_line++;
} }

View File

@ -139,6 +139,9 @@ void GB_free(GB_gameboy_t *gb)
if (gb->breakpoints) { if (gb->breakpoints) {
free(gb->breakpoints); free(gb->breakpoints);
} }
if (gb->sgb_screen_buffer) {
free(gb->sgb_screen_buffer);
}
#ifndef DISABLE_DEBUGGER #ifndef DISABLE_DEBUGGER
GB_debugger_clear_symbols(gb); GB_debugger_clear_symbols(gb);
#endif #endif
@ -642,6 +645,18 @@ void GB_reset(GB_gameboy_t *gb)
gb->sgb_player_count = 1; 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 */ /* Todo: Ugly, fixme, see comment in the timer state machine */
gb->div_state = 3; gb->div_state = 3;
@ -744,3 +759,13 @@ uint32_t GB_get_clock_rate(GB_gameboy_t *gb)
} }
return CPU_FREQUENCY * gb->clock_multiplier; 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;
}

View File

@ -475,6 +475,9 @@ struct GB_gameboy_internal_s {
bool sgb_ready_for_write; bool sgb_ready_for_write;
bool sgb_ready_for_stop; bool sgb_ready_for_stop;
bool sgb_disable_commands; bool sgb_disable_commands;
/* Screen buffer */
uint8_t *sgb_screen_buffer;
/* Multiplayer Input */ /* Multiplayer Input */
uint8_t sgb_player_count, sgb_current_player; uint8_t sgb_player_count, sgb_current_player;
); );
@ -678,4 +681,7 @@ uint32_t GB_get_clock_rate(GB_gameboy_t *gb);
#endif #endif
void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier); 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 */ #endif /* GB_h */

View File

@ -127,3 +127,29 @@ void GB_sgb_write(GB_gameboy_t *gb, uint8_t value)
break; 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;
}
}

View File

@ -4,6 +4,7 @@
#ifdef GB_INTERNAL #ifdef GB_INTERNAL
void GB_sgb_write(GB_gameboy_t *gb, uint8_t value); void GB_sgb_write(GB_gameboy_t *gb, uint8_t value);
void GB_sgb_render(GB_gameboy_t *gb);
#endif #endif
#endif #endif

View File

@ -21,6 +21,7 @@ void main()
vec2 position = gl_FragCoord.xy - origin; vec2 position = gl_FragCoord.xy - origin;
position /= output_resolution; position /= output_resolution;
position.y = 1 - position.y; position.y = 1 - position.y;
vec2 input_resolution = textureSize(image, 0);
if (mix_previous) { if (mix_previous) {
frag_color = mix(scale(image, position, input_resolution, output_resolution), frag_color = mix(scale(image, position, input_resolution, output_resolution),

View File

@ -3,7 +3,6 @@
#include <metal_math> #include <metal_math>
using namespace metal; using namespace metal;
constant float2 input_resolution = float2(160, 144);
/* For GLSL compatibility */ /* For GLSL compatibility */
typedef float2 vec2; typedef float2 vec2;
@ -49,6 +48,8 @@ fragment float4 fragment_shader(rasterizer_data in [[stage_in]],
constant bool *mix_previous [[ buffer(0) ]], constant bool *mix_previous [[ buffer(0) ]],
constant float2 *output_resolution [[ buffer(1) ]]) constant float2 *output_resolution [[ buffer(1) ]])
{ {
float2 input_resolution = float2(image.get_width(), image.get_height());
in.texcoords.y = 1 - in.texcoords.y; in.texcoords.y = 1 - in.texcoords.y;
if (*mix_previous) { if (*mix_previous) {
return mix(scale(image, in.texcoords, input_resolution, *output_resolution), return mix(scale(image, in.texcoords, input_resolution, *output_resolution),