snes9x/macosx/mac-render.mm

412 lines
11 KiB
Plaintext
Raw Normal View History

/*****************************************************************************\
Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
This file is licensed under the Snes9x License.
For further information, consult the LICENSE file in the root directory.
\*****************************************************************************/
2010-09-25 17:46:12 +02:00
/***********************************************************************************
SNES9X for Mac OS (c) Copyright John Stiles
Snes9x for Mac OS X
(c) Copyright 2001 - 2011 zones
2010-09-25 17:46:12 +02:00
(c) Copyright 2002 - 2005 107
(c) Copyright 2002 PB1400c
(c) Copyright 2004 Alexander and Sander
(c) Copyright 2004 - 2005 Steven Seeger
(c) Copyright 2005 Ryan Vogt
(c) Copyright 2019 Michael Donald Buckley
2010-09-25 17:46:12 +02:00
***********************************************************************************/
#import <Cocoa/Cocoa.h>
2010-09-25 17:46:12 +02:00
#include "snes9x.h"
2023-03-25 15:36:29 +01:00
#include "cheats.h"
2010-09-25 17:46:12 +02:00
#include "memmap.h"
#include "apu.h"
#include "display.h"
#include "blit.h"
#include <sys/time.h>
#include "mac-prefix.h"
#include "mac-os.h"
#include "mac-screenshot.h"
#include "mac-render.h"
2019-12-21 16:09:04 +01:00
static void S9xInitMetal (void);
static void S9xDeinitMetal(void);
static void S9xPutImageMetal (int, int, uint16 *);
2010-09-25 17:46:12 +02:00
static int whichBuf = 0;
static int textureNum = 0;
static int prevBlitWidth, prevBlitHeight;
static int imageWidth[2], imageHeight[2];
static int nx = 2;
2019-12-21 16:09:04 +01:00
typedef struct
{
vector_float2 position;
vector_float2 textureCoordinate;
} MetalVertex;
2010-09-25 17:46:12 +02:00
@interface MetalLayerDelegate: NSObject<CALayerDelegate, NSViewLayerContentScaleDelegate>
@end
@implementation MetalLayerDelegate
- (BOOL)layer:(CALayer *)layer shouldInheritContentsScale:(CGFloat)newScale fromWindow:(NSWindow *)window
{
return YES;
}
@end
2019-12-21 16:09:04 +01:00
CAMetalLayer *metalLayer = nil;
MetalLayerDelegate *layerDelegate = nil;
2019-12-21 16:09:04 +01:00
id<MTLDevice> metalDevice = nil;
id<MTLTexture> metalTexture = nil;
id<MTLCommandQueue> metalCommandQueue = nil;
id<MTLRenderPipelineState> metalPipelineState = nil;
2010-09-25 17:46:12 +02:00
void InitGraphics (void)
{
if (!S9xBlitFilterInit() |
!S9xBlit2xSaIFilterInit() |
!S9xBlitHQ2xFilterInit() |
!S9xBlitNTSCFilterInit())
QuitWithFatalError(@"render 02");
2010-09-25 17:46:12 +02:00
switch (videoMode)
{
default:
case VIDEOMODE_NTSC_C:
case VIDEOMODE_NTSC_TV_C:
S9xBlitNTSCFilterSet(&snes_ntsc_composite);
break;
case VIDEOMODE_NTSC_S:
case VIDEOMODE_NTSC_TV_S:
S9xBlitNTSCFilterSet(&snes_ntsc_svideo);
break;
case VIDEOMODE_NTSC_R:
case VIDEOMODE_NTSC_TV_R:
S9xBlitNTSCFilterSet(&snes_ntsc_rgb);
break;
case VIDEOMODE_NTSC_M:
case VIDEOMODE_NTSC_TV_M:
S9xBlitNTSCFilterSet(&snes_ntsc_monochrome);
break;
}
}
void DeinitGraphics (void)
{
S9xBlitNTSCFilterDeinit();
S9xBlitHQ2xFilterDeinit();
S9xBlit2xSaIFilterDeinit();
S9xBlitFilterDeinit();
}
void DrawFreezeDefrostScreen (uint8 *draw)
{
const int w = SNES_WIDTH << 1, h = SNES_HEIGHT << 1;
S9xPutImageMetal(w, h, (uint16 *)draw);
2010-09-25 17:46:12 +02:00
}
2019-12-21 16:09:04 +01:00
static void S9xInitMetal (void)
2010-09-25 17:46:12 +02:00
{
glScreenW = glScreenBounds.size.width;
glScreenH = glScreenBounds.size.height;
2010-09-25 17:46:12 +02:00
2019-12-21 16:09:04 +01:00
metalLayer = (CAMetalLayer *)s9xView.layer;
layerDelegate = [MetalLayerDelegate new];
metalLayer.delegate = layerDelegate;
2019-12-21 16:09:04 +01:00
metalDevice = s9xView.device;
metalCommandQueue = [metalDevice newCommandQueue];
2019-12-21 16:09:04 +01:00
NSError *error = nil;
id<MTLLibrary> defaultLibrary = [metalDevice newDefaultLibraryWithBundle:[NSBundle bundleForClass:[S9xEngine class]] error:&error];
MTLRenderPipelineDescriptor *pipelineDescriptor = [MTLRenderPipelineDescriptor new];
pipelineDescriptor.label = @"Snes9x Pipeline";
pipelineDescriptor.vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"];
pipelineDescriptor.colorAttachments[0].pixelFormat = s9xView.colorPixelFormat;
pipelineDescriptor.fragmentFunction = [defaultLibrary newFunctionWithName:@"fragmentShader"];
2019-12-21 16:09:04 +01:00
metalPipelineState = [metalDevice newRenderPipelineStateWithDescriptor:pipelineDescriptor error:&error];
2019-12-21 16:09:04 +01:00
if (metalPipelineState == nil)
2010-09-25 17:46:12 +02:00
{
2019-12-21 16:09:04 +01:00
NSLog(@"%@",error);
2010-09-25 17:46:12 +02:00
}
}
2019-12-21 16:09:04 +01:00
static void S9xDeinitMetal (void)
2010-09-25 17:46:12 +02:00
{
2019-12-21 16:09:04 +01:00
metalCommandQueue = nil;
metalDevice = nil;
metalTexture = nil;
metalLayer = nil;
2010-09-25 17:46:12 +02:00
}
void GetGameDisplay (int *w, int *h)
2010-09-25 17:46:12 +02:00
{
if (w != NULL && h != NULL)
{
*w = s9xView.frame.size.width;
*h = s9xView.frame.size.height;
}
}
void S9xInitDisplay (int argc, char **argv)
{
glScreenBounds = s9xView.frame;
2010-09-25 17:46:12 +02:00
unlimitedCursor = CGPointMake(0.0f, 0.0f);
imageWidth[0] = imageHeight[0] = 0;
imageWidth[1] = imageHeight[1] = 0;
prevBlitWidth = prevBlitHeight = 0;
whichBuf = 0;
textureNum = 0;
switch (videoMode)
{
case VIDEOMODE_HQ4X:
nx = 4;
break;
case VIDEOMODE_HQ3X:
nx = 3;
break;
case VIDEOMODE_NTSC_C:
case VIDEOMODE_NTSC_S:
case VIDEOMODE_NTSC_R:
case VIDEOMODE_NTSC_M:
nx = -1;
break;
case VIDEOMODE_NTSC_TV_C:
case VIDEOMODE_NTSC_TV_S:
case VIDEOMODE_NTSC_TV_R:
case VIDEOMODE_NTSC_TV_M:
nx = -2;
break;
default:
nx = 2;
break;
}
2019-12-21 16:09:04 +01:00
S9xInitMetal();
2010-09-25 17:46:12 +02:00
S9xSetSoundMute(false);
lastFrame = GetMicroseconds();
2010-09-25 17:46:12 +02:00
}
void S9xDeinitDisplay (void)
{
S9xSetSoundMute(true);
2019-12-21 16:09:04 +01:00
S9xDeinitMetal();
2010-09-25 17:46:12 +02:00
}
bool8 S9xInitUpdate (void)
{
return (true);
}
bool8 S9xDeinitUpdate (int width, int height)
{
S9xPutImage(width, height);
2019-12-21 16:09:04 +01:00
return true;
2010-09-25 17:46:12 +02:00
}
bool8 S9xContinueUpdate (int width, int height)
{
return (true);
}
void S9xPutImage (int width, int height)
{
2023-03-25 15:36:29 +01:00
for(unsigned int i = 0 ; i < sizeof(watches)/sizeof(*watches) ; i++)
{
if(watches[i].on)
{
int address = watches[i].address - 0x7E0000;
const uint8* source;
if(address < 0x20000)
{
source = Memory.RAM + address;
}
else if(address < 0x30000)
{
source = Memory.SRAM + address - 0x20000;
}
else
{
source = Memory.FillRAM + address - 0x30000;
}
memcpy(&(Cheat.CWatchRAM[address]), source, watches[i].size);
}
}
2010-09-25 17:46:12 +02:00
2019-12-21 16:09:04 +01:00
if (Settings.DisplayFrameRate)
{
static int drawnFrames[60] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
static int tableIndex = 0;
int frameCalc = 0;
2010-09-25 17:46:12 +02:00
2019-12-21 16:09:04 +01:00
drawnFrames[tableIndex] = skipFrames;
2019-12-21 16:09:04 +01:00
if (Settings.TurboMode)
{
2019-12-21 16:09:04 +01:00
drawnFrames[tableIndex] = (drawnFrames[tableIndex] + (macFastForwardRate / 2)) / macFastForwardRate;
if (drawnFrames[tableIndex] == 0)
drawnFrames[tableIndex] = 1;
}
2010-09-25 17:46:12 +02:00
2019-12-21 16:09:04 +01:00
tableIndex = (tableIndex + 1) % 60;
for (int i = 0; i < 60; i++)
frameCalc += drawnFrames[i];
2020-09-13 04:02:42 +02:00
// avoid dividing by 0
if (frameCalc == 0)
frameCalc = 1;
2019-12-21 16:09:04 +01:00
IPPU.DisplayedRenderedFrameCount = (Memory.ROMFramesPerSecond * 60) / frameCalc;
}
S9xPutImageMetal(width, height, GFX.Screen);
}
2019-12-21 16:09:04 +01:00
static void S9xPutImageMetal (int width, int height, uint16 *buffer16)
{
2022-04-11 01:16:16 +02:00
static uint8 *buffer = nil;
static int buffer_size = 0;
if (buffer_size != width * height * 4)
{
2022-04-11 01:20:59 +02:00
buffer = (uint8 *)realloc(buffer, width * height * 4);
2022-04-11 04:04:56 +02:00
buffer_size = width * height * 4;
2022-04-11 01:16:16 +02:00
}
2022-04-11 01:31:36 +02:00
for (int y = 0; y < height; y++)
2019-12-21 16:09:04 +01:00
{
2022-04-11 01:31:36 +02:00
for (int x = 0; x < width; x++)
{
uint16 pixel = buffer16[y * GFX.RealPPL + x];
unsigned int red = (pixel & FIRST_COLOR_MASK_RGB555) >> 10;
unsigned int green = (pixel & SECOND_COLOR_MASK_RGB555) >> 5;
unsigned int blue = (pixel & THIRD_COLOR_MASK_RGB555);
red = ( red * 527 + 23 ) >> 6;
green = ( green * 527 + 23 ) >> 6;
blue = ( blue * 527 + 23 ) >> 6;
int offset = (y * width + x) * 4;
buffer[offset++] = (uint8)red;
buffer[offset++] = (uint8)green;
buffer[offset++] = (uint8)blue;
buffer[offset] = 0xFF;
}
2019-12-21 16:09:04 +01:00
}
CGSize layerSize = metalLayer.bounds.size;
2022-11-05 20:08:09 +01:00
@autoreleasepool {
MTLTextureDescriptor *textureDescriptor = [MTLTextureDescriptor new];
textureDescriptor.pixelFormat = MTLPixelFormatRGBA8Unorm;
textureDescriptor.width = width;
textureDescriptor.height = height;
2019-12-21 16:09:04 +01:00
2022-11-05 20:08:09 +01:00
metalTexture = [metalDevice newTextureWithDescriptor:textureDescriptor];
2019-12-21 16:09:04 +01:00
2022-11-05 20:08:09 +01:00
[metalTexture replaceRegion:MTLRegionMake2D(0, 0, width, height) mipmapLevel:0 withBytes:buffer bytesPerRow:width * 4];
float vWidth = layerSize.width / 2.0;
float vHeight = layerSize.height / 2.0;
const MetalVertex verticies[] =
{
// Pixel positions, Texture coordinates
{ { vWidth, -vHeight }, { 1.f, 1.f } },
{ { -vWidth, -vHeight }, { 0.f, 1.f } },
{ { -vWidth, vHeight }, { 0.f, 0.f } },
{ { vWidth, -vHeight }, { 1.f, 1.f } },
{ { -vWidth, vHeight }, { 0.f, 0.f } },
{ { vWidth, vHeight }, { 1.f, 0.f } },
};
id<MTLBuffer> vertexBuffer = [metalDevice newBufferWithBytes:verticies length:sizeof(verticies) options:MTLResourceStorageModeShared];
id<MTLBuffer> fragmentBuffer = [metalDevice newBufferWithBytes:&videoMode length:sizeof(videoMode) options:MTLResourceStorageModeShared];
id<MTLCommandBuffer> commandBuffer = [metalCommandQueue commandBuffer];
commandBuffer.label = @"Snes9x command buffer";
id<CAMetalDrawable> drawable = [metalLayer nextDrawable];
MTLRenderPassDescriptor *renderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
renderPassDescriptor.colorAttachments[0].texture = drawable.texture;
renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear;
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.0,0.0,0.0,1.0);
if(renderPassDescriptor != nil)
{
id<MTLRenderCommandEncoder> renderEncoder =
[commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
renderEncoder.label = @"Snes9x render encoder";
vector_uint2 viewportSize = { static_cast<unsigned int>(layerSize.width), static_cast<unsigned int>(layerSize.height) };
CGFloat scale = metalLayer.contentsScale;
[renderEncoder setViewport:(MTLViewport){0.0, 0.0, layerSize.width * scale, layerSize.height * scale, -1.0, 1.0 }];
[renderEncoder setRenderPipelineState:metalPipelineState];
[renderEncoder setVertexBuffer:vertexBuffer
offset:0
atIndex:0];
[renderEncoder setVertexBytes:&viewportSize
length:sizeof(viewportSize)
atIndex:1];
[renderEncoder setFragmentTexture:metalTexture atIndex:0];
[renderEncoder setFragmentBuffer:fragmentBuffer offset:0 atIndex:1];
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:6];
[renderEncoder endEncoding];
[commandBuffer commit];
[commandBuffer waitUntilCompleted];
[drawable present];
}
}
2010-09-25 17:46:12 +02:00
}
void S9xTextMode (void)
{
return;
}
void S9xGraphicsMode (void)
{
return;
2020-09-13 04:02:42 +02:00
}