SGB resolution support (Cocoa only so far)
This commit is contained in:
parent
6ba5cfbeef
commit
634a54c046
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -13,4 +13,5 @@
|
||||
- (void) createInternalView;
|
||||
- (uint32_t *)currentBuffer;
|
||||
- (uint32_t *)previousBuffer;
|
||||
- (void)screenSizeChanged;
|
||||
@end
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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++;
|
||||
|
25
Core/gb.c
25
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;
|
||||
}
|
||||
|
@ -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;
|
||||
);
|
||||
@ -678,4 +681,7 @@ 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 */
|
||||
|
26
Core/sgb.c
26
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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -21,6 +21,7 @@ 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),
|
||||
|
@ -3,7 +3,6 @@
|
||||
#include <metal_math>
|
||||
|
||||
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),
|
||||
|
Loading…
Reference in New Issue
Block a user