In a simple Metal app that renders a grid of 2D Textures rotated and scaled I get the following strange artefacts (only on a MacBook Pro - on an iPad Pro the same code works perfectly). At some small regions on the screen (mainly little squares of ca. 10x10 pixels) the Fragment shader accesses the wrong texture, that is, NOT the one set by setFragmentTexture:atIndex:. These regions depend on the texture, are much smaller than the drawing primitive (the drawn triangle), and are on a fixed position on the screen, that is, they do not depend on the position in the texture. They remain on the same position, even if all other buffers (Vertex-Positions, Uniforms, etc.) are reloaded and changed, as long as at the particular screen position the same texture is displayed.
I am just switching from OpenGL to Metal. It could well be that I am doing something the wrong way. I have no idea what kind of mistake could provoke such a strange behavior... Could it be a hardware problem? Any ideas? (The same code works perfectly on an iPad though, with the only difference that MTLStorageModeShared is used for the textures and accordingly replaceRegion:... is not called)
Screenshots: https://nc.fhoermann.org/index.php/s/aM86J4g5LXHQYRZ
(The black regions are flickering all the time - I included two different outputs for the same encoding.)
I use a MacBook Pro (15 ", 2019), 2,3 GHz Intel Core i9, 16 GB 2400 MHz DDR4,
Intel UHD Graphics 630 1536 MB and Radeon Pro Vega 20 4 GB
macOS 10.14.5
Here are the relevant code fragments:
Initialization:
- (void)_loadMetalWithView:(nonnull MTKView *)view;
{
view.depthStencilPixelFormat = MTLPixelFormatInvalid;
view.colorPixelFormat = MTLPixelFormatBGRA8Unorm;
view.sampleCount = 1;
_mtlVertexDescriptor = [[MTLVertexDescriptor alloc] init];
_mtlVertexDescriptor.attributes[0].format = MTLVertexFormatFloat2;
_mtlVertexDescriptor.attributes[0].offset = 0;
_mtlVertexDescriptor.attributes[0].bufferIndex = 0;
_mtlVertexDescriptor.attributes[1].format = MTLVertexFormatFloat2;
_mtlVertexDescriptor.attributes[1].offset = 0;
_mtlVertexDescriptor.attributes[1].bufferIndex = 1;
_mtlVertexDescriptor.layouts[0].stride = 8;
_mtlVertexDescriptor.layouts[0].stepRate = 1;
_mtlVertexDescriptor.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex;
_mtlVertexDescriptor.layouts[1].stride = 8;
_mtlVertexDescriptor.layouts[1].stepRate = 1;
_mtlVertexDescriptor.layouts[1].stepFunction = MTLVertexStepFunctionPerVertex;
id<MTLLibrary> defaultLibrary = [_device newDefaultLibrary];
id <MTLFunction> vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"];
id <MTLFunction> fragmentFunction = [defaultLibrary newFunctionWithName:@"fragmentShader"];
MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
pipelineStateDescriptor.label = @"MyPipeline";
pipelineStateDescriptor.sampleCount = view.sampleCount;
pipelineStateDescriptor.vertexFunction = vertexFunction;
pipelineStateDescriptor.fragmentFunction = fragmentFunction;
pipelineStateDescriptor.vertexDescriptor = _mtlVertexDescriptor;
pipelineStateDescriptor.colorAttachments[0].pixelFormat = view.colorPixelFormat;
NSError *error = NULL;
_pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor error:&error];
if (!_pipelineState)
{
NSLog(@"Failed to created pipeline state, error %@", error);
}
_commandQueue = [_device newCommandQueue];
MTLTextureDescriptor *textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatR32Float
width:128
height:128
mipmapped:NO];
textureDescriptor.storageMode = MTLStorageModeManaged;
textureDescriptor.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite;
_Textures = [[NSMutableArray alloc] init];
for(int i=0;i<NMEMTEXTURE;i++)
{
[_Textures addObject:[_device newTextureWithDescriptor:textureDescriptor]];
}
}
Upload of the textures:
[_Textures[texture] replaceRegion:MTLRegionMake2D(0,0,128,128)
mipmapLevel:0
withBytes:pixels
bytesPerRow:(NSUInteger)(128*sizeof(float))];
Drawing Code:
- (void)drawInMTKView:(nonnull MTKView *)view
{
dispatch_semaphore_wait(_inFlightSemaphore, DISPATCH_TIME_FOREVER);
id <MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
commandBuffer.label = @"MyCommand";
__block dispatch_semaphore_t block_sema = _inFlightSemaphore;
[commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer)
{
dispatch_semaphore_signal(block_sema);
}];
MTLRenderPassDescriptor* renderPassDescriptor = view.currentRenderPassDescriptor;
if(renderPassDescriptor != nil) {
id <MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
renderEncoder.label = @"MyRenderEncoder";
[renderEncoder setRenderPipelineState:_pipelineState];
[renderEncoder setVertexBuffer:_dynamicUniformBuffer
offset:_uniformBufferOffset
atIndex:2];
[renderEncoder setFragmentBuffer:_dynamicUniformBuffer
offset:_uniformBufferOffset
atIndex:2];
[renderEncoder setVertexBuffer:_VertexPositions[list]
offset:0
atIndex:0];
[renderEncoder setVertexBuffer:_VertexGenerics[list]
offset:0
atIndex:1];
for(int i=0;i<vp->idrawrect[list];i++)
{
DRAWRECT *dr = &vp->drawrect[list][i];
[renderEncoder setFragmentTexture:_Renderer->_Textures[dr->texture[0]] atIndex:0];
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip
vertexStart:i*4
vertexCount:4];
}
[renderEncoder endEncoding];
[commandBuffer presentDrawable:view.currentDrawable];
}
[commandBuffer commit];
}
Shader:
typedef struct
{
matrix_float4x4 projectionMatrix;
matrix_float4x4 modelViewMatrix;
float params[6];
} Uniforms;
typedef struct
{
float2 position [[attribute(0)]];
float2 texCoord [[attribute(1)]];
} Vertex;
typedef struct
{
float4 position [[position]];
float2 texCoord;
} ColorInOut;
vertex ColorInOut vertexShader(Vertex in [[stage_in]],
constant Uniforms & uniforms [[ buffer(2) ]])
{
ColorInOut out;
float4 position = float4(in.position, 0.0, 1.0);
out.position = uniforms.projectionMatrix * uniforms.modelViewMatrix * position;
out.texCoord = in.texCoord;
return out;
}
fragment float4 fragmentShader(ColorInOut in [[stage_in]],
constant Uniforms & uniforms [[ buffer(2) ]],
texture2d<float> colorMap [[ texture(0) ]])
{
constexpr sampler colorSampler(mip_filter::none,
mag_filter::nearest,
min_filter::nearest);
int iter;
float rr = 0.5*colorMap.sample(colorSampler, in.texCoord.xy).r;
if(rr>=0.0)
iter = int(rr*2147483648.0);
else
iter = int(-rr*2147483648.0);
float i = float(iter)+0.5;
float4 c = float4(0.5*(1.0+sin(i*uniforms.params[0]+uniforms.params[1])),
0.5*(1.0+sin(i*uniforms.params[2]+uniforms.params[3])),
0.5*(1.0+sin(i*uniforms.params[4]+uniforms.params[5])), 1.0);
return c;
}
Thanks a lot!