metal: use a staging texture in SDL_UpdateTexture, to make sure it doesn't stomp texture data being drawn in a previous frame on the GPU.
parent
7b306bf34d
commit
37b1f989ce
|
@ -154,6 +154,7 @@ typedef struct METAL_ShaderPipelines
|
||||||
@property (nonatomic, assign) BOOL yuv;
|
@property (nonatomic, assign) BOOL yuv;
|
||||||
@property (nonatomic, assign) BOOL nv12;
|
@property (nonatomic, assign) BOOL nv12;
|
||||||
@property (nonatomic, assign) size_t conversionBufferOffset;
|
@property (nonatomic, assign) size_t conversionBufferOffset;
|
||||||
|
@property (nonatomic, assign) BOOL hasdata;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation METAL_TextureData
|
@implementation METAL_TextureData
|
||||||
|
@ -609,54 +610,141 @@ METAL_CreateTexture(SDL_Renderer * renderer, SDL_Texture * texture)
|
||||||
return 0;
|
return 0;
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
static void
|
||||||
|
METAL_UploadTextureData(id<MTLTexture> texture, SDL_Rect rect, int slice,
|
||||||
|
const void * pixels, int pitch)
|
||||||
|
{
|
||||||
|
[texture replaceRegion:MTLRegionMake2D(rect.x, rect.y, rect.w, rect.h)
|
||||||
|
mipmapLevel:0
|
||||||
|
slice:slice
|
||||||
|
withBytes:pixels
|
||||||
|
bytesPerRow:pitch
|
||||||
|
bytesPerImage:0];
|
||||||
|
}
|
||||||
|
|
||||||
|
static MTLStorageMode
|
||||||
|
METAL_GetStorageMode(id<MTLResource> resource)
|
||||||
|
{
|
||||||
|
/* iOS 8 does not have this method. */
|
||||||
|
if ([resource respondsToSelector:@selector(storageMode)]) {
|
||||||
|
return resource.storageMode;
|
||||||
|
}
|
||||||
|
return MTLStorageModeShared;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
METAL_UpdateTextureInternal(SDL_Renderer * renderer, METAL_TextureData *texturedata,
|
||||||
|
id<MTLTexture> texture, SDL_Rect rect, int slice,
|
||||||
|
const void * pixels, int pitch)
|
||||||
|
{
|
||||||
|
METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
|
||||||
|
SDL_Rect stagingrect = {0, 0, rect.w, rect.h};
|
||||||
|
MTLTextureDescriptor *desc;
|
||||||
|
|
||||||
|
/* If the texture is managed or shared and this is the first upload, we can
|
||||||
|
* use replaceRegion to upload to it directly. Otherwise we upload the data
|
||||||
|
* to a staging texture and copy that over. */
|
||||||
|
if (!texturedata.hasdata && METAL_GetStorageMode(texture) != MTLStorageModePrivate) {
|
||||||
|
METAL_UploadTextureData(texture, rect, slice, pixels, pitch);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:texture.pixelFormat
|
||||||
|
width:rect.w
|
||||||
|
height:rect.h
|
||||||
|
mipmapped:NO];
|
||||||
|
|
||||||
|
if (desc == nil) {
|
||||||
|
return SDL_OutOfMemory();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: We could have a pool of textures or a MTLHeap we allocate from,
|
||||||
|
* and release a staging texture back to the pool in the command buffer's
|
||||||
|
* completion handler. */
|
||||||
|
id<MTLTexture> stagingtex = [data.mtldevice newTextureWithDescriptor:desc];
|
||||||
|
if (stagingtex == nil) {
|
||||||
|
return SDL_OutOfMemory();
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !__has_feature(objc_arc)
|
||||||
|
[stagingtex autorelease];
|
||||||
|
#endif
|
||||||
|
|
||||||
|
METAL_UploadTextureData(stagingtex, stagingrect, 0, pixels, pitch);
|
||||||
|
|
||||||
|
if (data.mtlcmdencoder != nil) {
|
||||||
|
[data.mtlcmdencoder endEncoding];
|
||||||
|
data.mtlcmdencoder = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.mtlcmdbuffer == nil) {
|
||||||
|
data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer];
|
||||||
|
}
|
||||||
|
|
||||||
|
id<MTLBlitCommandEncoder> blitcmd = [data.mtlcmdbuffer blitCommandEncoder];
|
||||||
|
|
||||||
|
[blitcmd copyFromTexture:stagingtex
|
||||||
|
sourceSlice:0
|
||||||
|
sourceLevel:0
|
||||||
|
sourceOrigin:MTLOriginMake(0, 0, 0)
|
||||||
|
sourceSize:MTLSizeMake(rect.w, rect.h, 1)
|
||||||
|
toTexture:texture
|
||||||
|
destinationSlice:slice
|
||||||
|
destinationLevel:0
|
||||||
|
destinationOrigin:MTLOriginMake(rect.x, rect.y, 0)];
|
||||||
|
|
||||||
|
[blitcmd endEncoding];
|
||||||
|
|
||||||
|
/* TODO: This isn't very efficient for the YUV formats, which call
|
||||||
|
* UpdateTextureInternal multiple times in a row. */
|
||||||
|
[data.mtlcmdbuffer commit];
|
||||||
|
data.mtlcmdbuffer = nil;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
METAL_UpdateTexture(SDL_Renderer * renderer, SDL_Texture * texture,
|
METAL_UpdateTexture(SDL_Renderer * renderer, SDL_Texture * texture,
|
||||||
const SDL_Rect * rect, const void *pixels, int pitch)
|
const SDL_Rect * rect, const void *pixels, int pitch)
|
||||||
{ @autoreleasepool {
|
{ @autoreleasepool {
|
||||||
METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
|
METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
|
||||||
|
|
||||||
/* !!! FIXME: replaceRegion does not do any synchronization, so it might
|
if (METAL_UpdateTextureInternal(renderer, texturedata, texturedata.mtltexture, *rect, 0, pixels, pitch) < 0) {
|
||||||
* !!! FIXME: stomp on a previous frame's data that's currently being read
|
return -1;
|
||||||
* !!! FIXME: by the GPU. */
|
}
|
||||||
[texturedata.mtltexture replaceRegion:MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h)
|
|
||||||
mipmapLevel:0
|
|
||||||
withBytes:pixels
|
|
||||||
bytesPerRow:pitch];
|
|
||||||
|
|
||||||
if (texturedata.yuv) {
|
if (texturedata.yuv) {
|
||||||
int Uslice = texture->format == SDL_PIXELFORMAT_YV12 ? 1 : 0;
|
int Uslice = texture->format == SDL_PIXELFORMAT_YV12 ? 1 : 0;
|
||||||
int Vslice = texture->format == SDL_PIXELFORMAT_YV12 ? 0 : 1;
|
int Vslice = texture->format == SDL_PIXELFORMAT_YV12 ? 0 : 1;
|
||||||
|
int UVpitch = (pitch + 1) / 2;
|
||||||
|
SDL_Rect UVrect = {rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2};
|
||||||
|
|
||||||
/* Skip to the correct offset into the next texture */
|
/* Skip to the correct offset into the next texture */
|
||||||
pixels = (const void*)((const Uint8*)pixels + rect->h * pitch);
|
pixels = (const void*)((const Uint8*)pixels + rect->h * pitch);
|
||||||
[texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
|
if (METAL_UpdateTextureInternal(renderer, texturedata, texturedata.mtltexture_uv, UVrect, Uslice, pixels, UVpitch) < 0) {
|
||||||
mipmapLevel:0
|
return -1;
|
||||||
slice:Uslice
|
}
|
||||||
withBytes:pixels
|
|
||||||
bytesPerRow:(pitch + 1) / 2
|
|
||||||
bytesPerImage:0];
|
|
||||||
|
|
||||||
/* Skip to the correct offset into the next texture */
|
/* Skip to the correct offset into the next texture */
|
||||||
pixels = (const void*)((const Uint8*)pixels + ((rect->h + 1) / 2) * ((pitch + 1)/2));
|
pixels = (const void*)((const Uint8*)pixels + UVrect.h * UVpitch);
|
||||||
[texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
|
if (METAL_UpdateTextureInternal(renderer, texturedata, texturedata.mtltexture_uv, UVrect, Vslice, pixels, UVpitch) < 0) {
|
||||||
mipmapLevel:0
|
return -1;
|
||||||
slice:Vslice
|
}
|
||||||
withBytes:pixels
|
|
||||||
bytesPerRow:(pitch + 1) / 2
|
|
||||||
bytesPerImage:0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (texturedata.nv12) {
|
if (texturedata.nv12) {
|
||||||
|
SDL_Rect UVrect = {rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2};
|
||||||
|
int UVpitch = 2 * ((pitch + 1) / 2);
|
||||||
|
|
||||||
/* Skip to the correct offset into the next texture */
|
/* Skip to the correct offset into the next texture */
|
||||||
pixels = (const void*)((const Uint8*)pixels + rect->h * pitch);
|
pixels = (const void*)((const Uint8*)pixels + rect->h * pitch);
|
||||||
[texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
|
if (METAL_UpdateTextureInternal(renderer, texturedata, texturedata.mtltexture_uv, UVrect, 0, pixels, UVpitch) < 0) {
|
||||||
mipmapLevel:0
|
return -1;
|
||||||
slice:0
|
}
|
||||||
withBytes:pixels
|
|
||||||
bytesPerRow:2 * ((pitch + 1) / 2)
|
|
||||||
bytesPerImage:0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
texturedata.hasdata = YES;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
@ -670,30 +758,24 @@ METAL_UpdateTextureYUV(SDL_Renderer * renderer, SDL_Texture * texture,
|
||||||
METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
|
METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
|
||||||
const int Uslice = 0;
|
const int Uslice = 0;
|
||||||
const int Vslice = 1;
|
const int Vslice = 1;
|
||||||
|
SDL_Rect UVrect = {rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2};
|
||||||
|
|
||||||
/* Bail out if we're supposed to update an empty rectangle */
|
/* Bail out if we're supposed to update an empty rectangle */
|
||||||
if (rect->w <= 0 || rect->h <= 0) {
|
if (rect->w <= 0 || rect->h <= 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
[texturedata.mtltexture replaceRegion:MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h)
|
if (METAL_UpdateTextureInternal(renderer, texturedata, texturedata.mtltexture, *rect, 0, Yplane, Ypitch) < 0) {
|
||||||
mipmapLevel:0
|
return -1;
|
||||||
withBytes:Yplane
|
}
|
||||||
bytesPerRow:Ypitch];
|
if (METAL_UpdateTextureInternal(renderer, texturedata, texturedata.mtltexture_uv, UVrect, Uslice, Uplane, Upitch)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (METAL_UpdateTextureInternal(renderer, texturedata, texturedata.mtltexture_uv, UVrect, Vslice, Vplane, Vpitch)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
[texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
|
texturedata.hasdata = YES;
|
||||||
mipmapLevel:0
|
|
||||||
slice:Uslice
|
|
||||||
withBytes:Uplane
|
|
||||||
bytesPerRow:Upitch
|
|
||||||
bytesPerImage:0];
|
|
||||||
|
|
||||||
[texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
|
|
||||||
mipmapLevel:0
|
|
||||||
slice:Vslice
|
|
||||||
withBytes:Vplane
|
|
||||||
bytesPerRow:Vpitch
|
|
||||||
bytesPerImage:0];
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}}
|
}}
|
||||||
|
@ -1217,7 +1299,7 @@ METAL_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect,
|
||||||
* update the CPU-side copy of the texture data.
|
* update the CPU-side copy of the texture data.
|
||||||
* NOTE: Currently all of our textures are managed on macOS. We'll need some
|
* NOTE: Currently all of our textures are managed on macOS. We'll need some
|
||||||
* extra copying for any private textures. */
|
* extra copying for any private textures. */
|
||||||
if (mtltexture.storageMode == MTLStorageModeManaged) {
|
if (METAL_GetStorageMode(mtltexture) == MTLStorageModeManaged) {
|
||||||
id<MTLBlitCommandEncoder> blit = [data.mtlcmdbuffer blitCommandEncoder];
|
id<MTLBlitCommandEncoder> blit = [data.mtlcmdbuffer blitCommandEncoder];
|
||||||
[blit synchronizeResource:mtltexture];
|
[blit synchronizeResource:mtltexture];
|
||||||
[blit endEncoding];
|
[blit endEncoding];
|
||||||
|
|
Loading…
Reference in New Issue