2018-06-20 21:29:53 -07:00
|
|
|
//
|
|
|
|
// Context.m
|
|
|
|
// MetalRenderer
|
|
|
|
//
|
|
|
|
// Created by Stuart Carnie on 6/9/18.
|
|
|
|
// Copyright © 2018 Stuart Carnie. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
#import "Context.h"
|
2018-06-29 22:57:48 -07:00
|
|
|
#import "Filter.h"
|
2018-06-20 21:29:53 -07:00
|
|
|
#import <QuartzCore/QuartzCore.h>
|
|
|
|
|
2018-06-29 22:57:48 -07:00
|
|
|
@interface Texture()
|
|
|
|
@property (readwrite) id<MTLTexture> texture;
|
|
|
|
@property (readwrite) id<MTLSamplerState> sampler;
|
|
|
|
@end
|
|
|
|
|
2018-06-20 21:29:53 -07:00
|
|
|
@interface Context()
|
2018-06-29 22:57:48 -07:00
|
|
|
@property (readonly) id<MTLCommandBuffer> blitCommandBuffer;
|
|
|
|
- (bool)_initConversionFilters;
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation Context
|
2018-06-20 21:29:53 -07:00
|
|
|
{
|
2018-06-29 22:57:48 -07:00
|
|
|
id<MTLCommandQueue> _commandQueue;
|
2018-06-20 21:29:53 -07:00
|
|
|
CAMetalLayer *_layer;
|
|
|
|
id<CAMetalDrawable> _drawable;
|
2018-06-29 22:57:48 -07:00
|
|
|
id<MTLSamplerState> _samplers[TEXTURE_FILTER_MIPMAP_NEAREST + 1];
|
|
|
|
Filter *_filters[RPixelFormatCount]; // convert to bgra8888
|
|
|
|
|
|
|
|
id<MTLCommandBuffer> _blitCommandBuffer;
|
2018-06-20 21:29:53 -07:00
|
|
|
}
|
|
|
|
|
2018-06-29 22:57:48 -07:00
|
|
|
- (instancetype)initWithDevice:(id<MTLDevice>)d
|
|
|
|
layer:(CAMetalLayer *)layer
|
|
|
|
library:(id<MTLLibrary>)l
|
|
|
|
{
|
|
|
|
if (self = [super init])
|
|
|
|
{
|
|
|
|
_device = d;
|
|
|
|
_layer = layer;
|
|
|
|
_library = l;
|
|
|
|
_commandQueue = [_device newCommandQueue];
|
|
|
|
|
|
|
|
MTLSamplerDescriptor *sd = [MTLSamplerDescriptor new];
|
|
|
|
|
|
|
|
sd.label = @"NEAREST";
|
|
|
|
_samplers[TEXTURE_FILTER_NEAREST] = [d newSamplerStateWithDescriptor:sd];
|
|
|
|
|
|
|
|
sd.mipFilter = MTLSamplerMipFilterNearest;
|
|
|
|
sd.label = @"MIPMAP_NEAREST";
|
|
|
|
_samplers[TEXTURE_FILTER_MIPMAP_NEAREST] = [d newSamplerStateWithDescriptor:sd];
|
|
|
|
|
|
|
|
sd.mipFilter = MTLSamplerMipFilterNotMipmapped;
|
|
|
|
sd.minFilter = MTLSamplerMinMagFilterLinear;
|
|
|
|
sd.magFilter = MTLSamplerMinMagFilterLinear;
|
|
|
|
sd.label = @"LINEAR";
|
|
|
|
_samplers[TEXTURE_FILTER_LINEAR] = [d newSamplerStateWithDescriptor:sd];
|
|
|
|
|
|
|
|
sd.mipFilter = MTLSamplerMipFilterLinear;
|
|
|
|
sd.label = @"MIPMAP_LINEAR";
|
|
|
|
_samplers[TEXTURE_FILTER_MIPMAP_LINEAR] = [d newSamplerStateWithDescriptor:sd];
|
|
|
|
|
|
|
|
if (![self _initConversionFilters])
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
return self;
|
|
|
|
}
|
2018-06-20 21:29:53 -07:00
|
|
|
|
2018-06-29 22:57:48 -07:00
|
|
|
- (bool)_initConversionFilters
|
2018-06-20 21:29:53 -07:00
|
|
|
{
|
2018-06-29 22:57:48 -07:00
|
|
|
NSError *err = nil;
|
|
|
|
_filters[RPixelFormatBGRA4Unorm] = [Filter newFilterWithFunctionName:@"convert_bgra4444_to_bgra8888"
|
|
|
|
device:_device
|
|
|
|
library:_library
|
|
|
|
error:&err];
|
|
|
|
if (err)
|
|
|
|
{
|
|
|
|
RARCH_LOG("[Metal]: unable to create 'convert_bgra4444_to_bgra8888' conversion filter: %s\n",
|
|
|
|
err.localizedDescription.UTF8String);
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
|
|
|
_filters[RPixelFormatB5G6R5Unorm] = [Filter newFilterWithFunctionName:@"convert_rgb565_to_bgra8888"
|
|
|
|
device:_device
|
|
|
|
library:_library
|
|
|
|
error:&err];
|
|
|
|
if (err)
|
|
|
|
{
|
|
|
|
RARCH_LOG("[Metal]: unable to create 'convert_rgb565_to_bgra8888' conversion filter: %s\n",
|
|
|
|
err.localizedDescription.UTF8String);
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
|
|
|
return YES;
|
|
|
|
}
|
2018-06-20 21:29:53 -07:00
|
|
|
|
2018-06-29 22:57:48 -07:00
|
|
|
- (Texture *)newTexture:(struct texture_image)image filter:(enum texture_filter_type)filter
|
|
|
|
{
|
|
|
|
if (!image.pixels && !image.width && !image.height)
|
|
|
|
{
|
|
|
|
/* Create a dummy texture instead. */
|
|
|
|
#define T0 0xff000000u
|
|
|
|
#define T1 0xffffffffu
|
|
|
|
static const uint32_t checkerboard[] = {
|
|
|
|
T0, T1, T0, T1, T0, T1, T0, T1,
|
|
|
|
T1, T0, T1, T0, T1, T0, T1, T0,
|
|
|
|
T0, T1, T0, T1, T0, T1, T0, T1,
|
|
|
|
T1, T0, T1, T0, T1, T0, T1, T0,
|
|
|
|
T0, T1, T0, T1, T0, T1, T0, T1,
|
|
|
|
T1, T0, T1, T0, T1, T0, T1, T0,
|
|
|
|
T0, T1, T0, T1, T0, T1, T0, T1,
|
|
|
|
T1, T0, T1, T0, T1, T0, T1, T0,
|
|
|
|
};
|
|
|
|
#undef T0
|
|
|
|
#undef T1
|
|
|
|
|
|
|
|
image.pixels = (uint32_t *)checkerboard;
|
|
|
|
image.width = 8;
|
|
|
|
image.height = 8;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(sgc): mipmapping is not working
|
|
|
|
BOOL mipmapped = filter == TEXTURE_FILTER_MIPMAP_LINEAR || filter == TEXTURE_FILTER_MIPMAP_NEAREST;
|
|
|
|
|
|
|
|
MTLTextureDescriptor *td = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm
|
|
|
|
width:image.width
|
|
|
|
height:image.height
|
|
|
|
mipmapped:mipmapped];
|
|
|
|
|
|
|
|
id<MTLTexture> t = [_device newTextureWithDescriptor:td];
|
|
|
|
[t replaceRegion:MTLRegionMake2D(0, 0, image.width, image.height)
|
|
|
|
mipmapLevel:0
|
|
|
|
withBytes:image.pixels
|
|
|
|
bytesPerRow:4 * image.width];
|
|
|
|
|
|
|
|
if (mipmapped)
|
|
|
|
{
|
|
|
|
id<MTLCommandBuffer> cb = self.blitCommandBuffer;
|
|
|
|
id<MTLBlitCommandEncoder> bce = [cb blitCommandEncoder];
|
|
|
|
[bce generateMipmapsForTexture:t];
|
|
|
|
[bce endEncoding];
|
|
|
|
}
|
|
|
|
|
|
|
|
Texture *tex = [Texture new];
|
|
|
|
tex.texture = t;
|
|
|
|
tex.sampler = _samplers[filter];
|
|
|
|
|
|
|
|
return tex;
|
2018-06-20 21:29:53 -07:00
|
|
|
}
|
|
|
|
|
2018-06-29 22:57:48 -07:00
|
|
|
- (id<CAMetalDrawable>)nextDrawable
|
|
|
|
{
|
|
|
|
if (_drawable == nil)
|
|
|
|
{
|
2018-06-20 21:29:53 -07:00
|
|
|
_drawable = _layer.nextDrawable;
|
|
|
|
}
|
|
|
|
return _drawable;
|
|
|
|
}
|
|
|
|
|
2018-06-29 22:57:48 -07:00
|
|
|
- (id<MTLTexture>)renderTexture
|
|
|
|
{
|
2018-06-20 21:29:53 -07:00
|
|
|
return self.nextDrawable.texture;
|
|
|
|
}
|
|
|
|
|
2018-06-29 22:57:48 -07:00
|
|
|
- (void)convertFormat:(RPixelFormat)fmt from:(id<MTLBuffer>)src to:(id<MTLTexture>)dst
|
|
|
|
{
|
|
|
|
assert(dst.width * dst.height == src.length / RPixelFormatToBPP(fmt));
|
|
|
|
assert(fmt >= 0 && fmt < RPixelFormatCount);
|
|
|
|
Filter *conv = _filters[fmt];
|
|
|
|
assert(conv != nil);
|
|
|
|
[conv apply:self.commandBuffer inBuf:src outTex:dst];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (id<MTLCommandBuffer>)blitCommandBuffer
|
|
|
|
{
|
|
|
|
if (!_blitCommandBuffer)
|
|
|
|
_blitCommandBuffer = [_commandQueue commandBuffer];
|
|
|
|
return _blitCommandBuffer;
|
|
|
|
}
|
|
|
|
|
2018-06-20 21:29:53 -07:00
|
|
|
- (void)begin
|
|
|
|
{
|
|
|
|
assert(_commandBuffer == nil);
|
|
|
|
_commandBuffer = [_commandQueue commandBuffer];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)end
|
|
|
|
{
|
|
|
|
assert(self->_commandBuffer != nil);
|
2018-06-29 22:57:48 -07:00
|
|
|
|
|
|
|
if (_blitCommandBuffer)
|
|
|
|
{
|
|
|
|
// pending blits for mipmaps
|
|
|
|
[_blitCommandBuffer commit];
|
|
|
|
[_blitCommandBuffer waitUntilCompleted];
|
|
|
|
_blitCommandBuffer = nil;
|
|
|
|
}
|
|
|
|
|
2018-06-20 21:29:53 -07:00
|
|
|
[_commandBuffer commit];
|
|
|
|
_commandBuffer = nil;
|
|
|
|
_drawable = nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
2018-06-29 22:57:48 -07:00
|
|
|
|
|
|
|
@implementation Texture
|
|
|
|
@end
|