metal: more robust render pass handling.

Initial implementation of captureScreenshot.
Fix getStats().shaderswitches.
Fix getStats().drawcalls.
This commit is contained in:
Alex Szpakowski
2021-04-04 23:47:58 -03:00
parent 8c88d645a3
commit 2ec9f88f7a
4 changed files with 136 additions and 85 deletions
+10 -3
View File
@@ -39,6 +39,12 @@ class Graphics final : public love::graphics::Graphics
{ {
public: public:
enum SubmitType
{
SUBMIT_DONE,
SUBMIT_STORE,
};
struct RenderEncoderBindings struct RenderEncoderBindings
{ {
void *textures[32][ShaderStage::STAGE_MAX_ENUM]; void *textures[32][ShaderStage::STAGE_MAX_ENUM];
@@ -107,15 +113,15 @@ public:
bool usesGLSLES() const override; bool usesGLSLES() const override;
RendererInfo getRendererInfo() const override; RendererInfo getRendererInfo() const override;
void attachShader(love::graphics::Shader *shader); void setShaderChanged();
id<MTLCommandBuffer> useCommandBuffer(); id<MTLCommandBuffer> useCommandBuffer();
id<MTLCommandBuffer> getCommandBuffer() const { return commandBuffer; } id<MTLCommandBuffer> getCommandBuffer() const { return commandBuffer; }
void submitCommandBuffer(); void submitCommandBuffer(SubmitType type);
id<MTLRenderCommandEncoder> useRenderEncoder(); id<MTLRenderCommandEncoder> useRenderEncoder();
id<MTLRenderCommandEncoder> getRenderEncoder() const { return renderEncoder; } id<MTLRenderCommandEncoder> getRenderEncoder() const { return renderEncoder; }
void submitRenderEncoder(); void submitRenderEncoder(SubmitType type);
id<MTLBlitCommandEncoder> useBlitEncoder(); id<MTLBlitCommandEncoder> useBlitEncoder();
id<MTLBlitCommandEncoder> getBlitEncoder() const { return blitEncoder; } id<MTLBlitCommandEncoder> getBlitEncoder() const { return blitEncoder; }
@@ -206,6 +212,7 @@ private:
uint32 dirtyRenderState; uint32 dirtyRenderState;
VertexAttributes lastVertexAttributes; VertexAttributes lastVertexAttributes;
bool windowHasStencil; bool windowHasStencil;
int shaderSwitches;
StrongRef<love::graphics::Texture> backbufferMSAA; StrongRef<love::graphics::Texture> backbufferMSAA;
StrongRef<love::graphics::Texture> backbufferDepthStencil; StrongRef<love::graphics::Texture> backbufferDepthStencil;
+124 -80
View File
@@ -231,6 +231,7 @@ Graphics::Graphics()
, passDesc(nil) , passDesc(nil)
, dirtyRenderState(STATEBIT_ALL) , dirtyRenderState(STATEBIT_ALL)
, windowHasStencil(false) , windowHasStencil(false)
, shaderSwitches(0)
, requestedBackbufferMSAA(0) , requestedBackbufferMSAA(0)
, attachmentStoreActions() , attachmentStoreActions()
, renderBindings() , renderBindings()
@@ -331,7 +332,7 @@ Graphics::Graphics()
Graphics::~Graphics() Graphics::~Graphics()
{ @autoreleasepool { { @autoreleasepool {
submitCommandBuffer(); submitCommandBuffer(SUBMIT_DONE);
delete uniformBuffer; delete uniformBuffer;
delete defaultAttributesBuffer; delete defaultAttributesBuffer;
passDesc = nil; passDesc = nil;
@@ -441,7 +442,7 @@ void Graphics::unSetMode()
flushBatchedDraws(); flushBatchedDraws();
submitCommandBuffer(); submitCommandBuffer(SUBMIT_DONE);
for (auto temp : temporaryTextures) for (auto temp : temporaryTextures)
temp.texture->release(); temp.texture->release();
@@ -459,9 +460,10 @@ void Graphics::setActive(bool enable)
active = enable; active = enable;
} }
void Graphics::attachShader(love::graphics::Shader *shader) void Graphics::setShaderChanged()
{ {
dirtyRenderState |= STATE_SHADER; dirtyRenderState |= STATE_SHADER;
++shaderSwitches;
} }
id<MTLCommandBuffer> Graphics::useCommandBuffer() id<MTLCommandBuffer> Graphics::useCommandBuffer()
@@ -481,9 +483,9 @@ id<MTLCommandBuffer> Graphics::useCommandBuffer()
return commandBuffer; return commandBuffer;
} }
void Graphics::submitCommandBuffer() void Graphics::submitCommandBuffer(SubmitType type)
{ {
submitRenderEncoder(); submitRenderEncoder(type);
submitBlitEncoder(); submitBlitEncoder();
if (commandBuffer != nil) if (commandBuffer != nil)
@@ -570,17 +572,6 @@ id<MTLRenderCommandEncoder> Graphics::useRenderEncoder()
renderBindings = {}; renderBindings = {};
for (int i = 0; i < MAX_COLOR_RENDER_TARGETS; i++)
{
passDesc.colorAttachments[0].texture = nil;
passDesc.colorAttachments[0].resolveTexture = nil;
}
passDesc.depthAttachment.texture = nil;
passDesc.depthAttachment.resolveTexture = nil;
passDesc.stencilAttachment.texture = nil;
passDesc.stencilAttachment.resolveTexture = nil;
id<MTLBuffer> defaultbuffer = getMTLBuffer(defaultAttributesBuffer); id<MTLBuffer> defaultbuffer = getMTLBuffer(defaultAttributesBuffer);
setBuffer(renderEncoder, renderBindings, ShaderStage::STAGE_VERTEX, DEFAULT_VERTEX_BUFFER_BINDING, defaultbuffer, 0); setBuffer(renderEncoder, renderBindings, ShaderStage::STAGE_VERTEX, DEFAULT_VERTEX_BUFFER_BINDING, defaultbuffer, 0);
@@ -590,28 +581,45 @@ id<MTLRenderCommandEncoder> Graphics::useRenderEncoder()
return renderEncoder; return renderEncoder;
} }
void Graphics::submitRenderEncoder() void Graphics::submitRenderEncoder(SubmitType type)
{ {
if (renderEncoder != nil) if (renderEncoder != nil)
{ {
bool store = type == SUBMIT_STORE;
const auto &actions = attachmentStoreActions; const auto &actions = attachmentStoreActions;
const auto &rts = states.back().renderTargets; const auto &rts = states.back().renderTargets;
bool isbackbuffer = rts.getFirstTarget().texture.get() == nullptr; bool isbackbuffer = rts.getFirstTarget().texture.get() == nullptr;
if (isbackbuffer) if (isbackbuffer)
[renderEncoder setColorStoreAction:actions.color[0] atIndex:0]; [renderEncoder setColorStoreAction:(store ? MTLStoreActionStore : actions.color[0]) atIndex:0];
for (size_t i = 0; i < rts.colors.size(); i++) for (size_t i = 0; i < rts.colors.size(); i++)
[renderEncoder setColorStoreAction:actions.color[i] atIndex:i]; [renderEncoder setColorStoreAction:(store ? MTLStoreActionStore : actions.color[i]) atIndex:i];
if (rts.depthStencil.texture.get() || rts.temporaryRTFlags != 0 || isbackbuffer) if (rts.depthStencil.texture.get() || rts.temporaryRTFlags != 0 || isbackbuffer)
{ {
[renderEncoder setDepthStoreAction:actions.depth]; [renderEncoder setDepthStoreAction:store ? MTLStoreActionStore : actions.depth];
[renderEncoder setStencilStoreAction:actions.stencil]; [renderEncoder setStencilStoreAction:store ? MTLStoreActionStore : actions.stencil];
} }
[renderEncoder endEncoding]; [renderEncoder endEncoding];
renderEncoder = nil; renderEncoder = nil;
// Reset actions to load. The next clear/discard/etc will set more
// appropriate actions if necessary.
for (int i = 0; i < MAX_COLOR_RENDER_TARGETS; i++)
{
passDesc.colorAttachments[i].loadAction = MTLLoadActionLoad;
passDesc.colorAttachments[i].texture = nil;
passDesc.colorAttachments[i].resolveTexture = nil;
}
passDesc.depthAttachment.loadAction = MTLLoadActionLoad;
passDesc.depthAttachment.texture = nil;
passDesc.depthAttachment.resolveTexture = nil;
passDesc.stencilAttachment.loadAction = MTLLoadActionLoad;
passDesc.stencilAttachment.texture = nil;
passDesc.stencilAttachment.resolveTexture = nil;
} }
} }
@@ -619,7 +627,7 @@ id<MTLBlitCommandEncoder> Graphics::useBlitEncoder()
{ {
if (blitEncoder == nil) if (blitEncoder == nil)
{ {
submitRenderEncoder(); submitRenderEncoder(SUBMIT_STORE);
blitEncoder = [useCommandBuffer() blitCommandEncoder]; blitEncoder = [useCommandBuffer() blitCommandEncoder];
} }
@@ -985,6 +993,8 @@ void Graphics::draw(const DrawCommand &cmd)
vertexStart:cmd.vertexStart vertexStart:cmd.vertexStart
vertexCount:cmd.vertexCount vertexCount:cmd.vertexCount
instanceCount:cmd.instanceCount]; instanceCount:cmd.instanceCount];
++drawCalls;
}} }}
void Graphics::draw(const DrawIndexedCommand &cmd) void Graphics::draw(const DrawIndexedCommand &cmd)
@@ -1006,6 +1016,8 @@ void Graphics::draw(const DrawIndexedCommand &cmd)
indexBuffer:getMTLBuffer(cmd.indexBuffer) indexBuffer:getMTLBuffer(cmd.indexBuffer)
indexBufferOffset:cmd.indexBufferOffset indexBufferOffset:cmd.indexBufferOffset
instanceCount:cmd.instanceCount]; instanceCount:cmd.instanceCount];
++drawCalls;
}} }}
void Graphics::drawQuads(int start, int count, const VertexAttributes &attributes, const BufferBindings &buffers, love::graphics::Texture *texture) void Graphics::drawQuads(int start, int count, const VertexAttributes &attributes, const BufferBindings &buffers, love::graphics::Texture *texture)
@@ -1099,13 +1111,14 @@ void Graphics::endPass()
auto &rts = states.back().renderTargets; auto &rts = states.back().renderTargets;
love::graphics::Texture *depthstencil = rts.depthStencil.texture.get(); love::graphics::Texture *depthstencil = rts.depthStencil.texture.get();
// Discard the depth/stencil buffer if we're using an internal cached one. // Discard the depth/stencil buffer if we're using an internal cached one,
// or if this is the backbuffer.
if (depthstencil == nullptr && (rts.temporaryRTFlags & (TEMPORARY_RT_DEPTH | TEMPORARY_RT_STENCIL)) != 0) if (depthstencil == nullptr && (rts.temporaryRTFlags & (TEMPORARY_RT_DEPTH | TEMPORARY_RT_STENCIL)) != 0)
discard({}, true); discard({}, true);
else if (!rts.getFirstTarget().texture.get()) else if (!rts.getFirstTarget().texture.get())
discard({}, true); // Backbuffer discard({}, true); // Backbuffer
submitRenderEncoder(); submitRenderEncoder(SUBMIT_DONE);
for (const auto &rt : rts.colors) for (const auto &rt : rts.colors)
{ {
@@ -1117,9 +1130,17 @@ void Graphics::endPass()
void Graphics::clear(OptionalColorf c, OptionalInt stencil, OptionalDouble depth) void Graphics::clear(OptionalColorf c, OptionalInt stencil, OptionalDouble depth)
{ @autoreleasepool { { @autoreleasepool {
if (c.hasValue || stencil.hasValue || depth.hasValue) if (c.hasValue || stencil.hasValue || depth.hasValue)
{
flushBatchedDraws(); flushBatchedDraws();
// TODO: handle clearing mid-pass // Handle clearing mid-pass by starting a new pass.
if (renderEncoder != nil)
{
submitRenderEncoder(SUBMIT_STORE);
useRenderEncoder();
}
}
if (c.hasValue) if (c.hasValue)
{ {
gammaCorrectColor(c.value); gammaCorrectColor(c.value);
@@ -1160,7 +1181,13 @@ void Graphics::clear(const std::vector<OptionalColorf> &colors, OptionalInt sten
flushBatchedDraws(); flushBatchedDraws();
// TODO: handle clearing mid-pass // Handle clearing mid-pass by starting a new pass.
if (renderEncoder != nil)
{
submitRenderEncoder(SUBMIT_STORE);
useRenderEncoder();
}
for (int i = 0; i < ncolors; i++) for (int i = 0; i < ncolors; i++)
{ {
if (!colors[i].hasValue) if (!colors[i].hasValue)
@@ -1204,62 +1231,31 @@ void Graphics::present(void *screenshotCallbackData)
endPass(); endPass();
id<MTLBuffer> screenshotbuffer = nil;
if (!pendingScreenshotCallbacks.empty()) if (!pendingScreenshotCallbacks.empty())
{ {
int w = getPixelWidth(); int w = activeDrawable.texture.width;
int h = getPixelHeight(); int h = activeDrawable.texture.height;
size_t size = w * h * 4;
size_t row = 4 * w; screenshotbuffer = [device newBufferWithLength:size options:MTLResourceStorageModeShared];
size_t size = row * h; if (screenshotbuffer == nil)
throw love::Exception("Out of graphics memory.");
uint8 *screenshot = nullptr; auto blitencoder = useBlitEncoder();
try [blitencoder copyFromTexture:activeDrawable.texture
{ sourceSlice:0
screenshot = new uint8[size]; sourceLevel:0
} sourceOrigin:MTLOriginMake(0, 0, 0)
catch (std::exception &) sourceSize:MTLSizeMake(w, h, 0)
{ toBuffer:screenshotbuffer
delete[] screenshot; destinationOffset:0
throw love::Exception("Out of memory."); destinationBytesPerRow:w * 4
} destinationBytesPerImage:size];
// TODO submitBlitEncoder();
// Replace alpha values with full opacity.
for (size_t i = 3; i < size; i += 4)
screenshot[i] = 255;
auto imagemodule = Module::getInstance<love::image::Image>(M_IMAGE);
for (int i = 0; i < (int) pendingScreenshotCallbacks.size(); i++)
{
const auto &info = pendingScreenshotCallbacks[i];
image::ImageData *img = nullptr;
try
{
img = imagemodule->newImageData(w, h, PIXELFORMAT_RGBA8_UNORM, screenshot);
}
catch (love::Exception &)
{
delete[] screenshot;
info.callback(&info, nullptr, nullptr);
for (int j = i + 1; j < (int) pendingScreenshotCallbacks.size(); j++)
{
const auto &ninfo = pendingScreenshotCallbacks[j];
ninfo.callback(&ninfo, nullptr, nullptr);
}
pendingScreenshotCallbacks.clear();
throw;
}
info.callback(&info, img, screenshotCallbackData);
img->release();
}
delete[] screenshot;
pendingScreenshotCallbacks.clear();
} }
for (StreamBuffer *buffer : batchedDrawState.vb) for (StreamBuffer *buffer : batchedDrawState.vb)
@@ -1275,7 +1271,56 @@ void Graphics::present(void *screenshotCallbackData)
if (cmd != nil && activeDrawable != nil) if (cmd != nil && activeDrawable != nil)
[cmd presentDrawable:activeDrawable]; [cmd presentDrawable:activeDrawable];
submitCommandBuffer(); submitCommandBuffer(SUBMIT_DONE);
if (!pendingScreenshotCallbacks.empty())
{
[cmd waitUntilCompleted];
int w = activeDrawable.texture.width;
int h = activeDrawable.texture.height;
size_t size = w * h * 4;
auto imagemodule = Module::getInstance<love::image::Image>(M_IMAGE);
for (int i = 0; i < (int) pendingScreenshotCallbacks.size(); i++)
{
const auto &info = pendingScreenshotCallbacks[i];
image::ImageData *img = nullptr;
try
{
img = imagemodule->newImageData(w, h, PIXELFORMAT_RGBA8_UNORM, screenshotbuffer.contents);
}
catch (love::Exception &)
{
info.callback(&info, nullptr, nullptr);
for (int j = i + 1; j < (int) pendingScreenshotCallbacks.size(); j++)
{
const auto &ninfo = pendingScreenshotCallbacks[j];
ninfo.callback(&ninfo, nullptr, nullptr);
}
pendingScreenshotCallbacks.clear();
throw;
}
uint8 *screenshot = (uint8 *) img->getData();
// Convert from BGRA to RGBA and replace alpha with full opacity.
for (size_t i = 0; i < size; i += 4)
{
uint8 r = screenshot[i + 2];
screenshot[i + 2] = screenshot[i + 0];
screenshot[i + 0] = r;
screenshot[i + 3] = 255;
}
info.callback(&info, img, screenshotCallbackData);
img->release();
}
pendingScreenshotCallbacks.clear();
}
auto window = Module::getInstance<love::window::Window>(M_WINDOW); auto window = Module::getInstance<love::window::Window>(M_WINDOW);
if (window != nullptr) if (window != nullptr)
@@ -1288,7 +1333,7 @@ void Graphics::present(void *screenshotCallbackData)
// Reset the per-frame stat counts. // Reset the per-frame stat counts.
drawCalls = 0; drawCalls = 0;
//gl.stats.shaderSwitches = 0; shaderSwitches = 0;
renderTargetSwitchCount = 0; renderTargetSwitchCount = 0;
drawCallsBatched = 0; drawCallsBatched = 0;
@@ -1806,8 +1851,7 @@ void Graphics::initCapabilities()
void Graphics::getAPIStats(int &shaderswitches) const void Graphics::getAPIStats(int &shaderswitches) const
{ {
// TODO shaderswitches = shaderSwitches;
shaderswitches = 0;
} }
} // metal } // metal
+1 -1
View File
@@ -737,7 +737,7 @@ void Shader::attach()
{ {
Graphics *gfx = Graphics::getInstance(); Graphics *gfx = Graphics::getInstance();
gfx->flushBatchedDraws(); gfx->flushBatchedDraws();
gfx->attachShader(this); gfx->setShaderChanged();
current = this; current = this;
} }
} }
+1 -1
View File
@@ -245,7 +245,7 @@ void Texture::readbackImageData(love::image::ImageData *imagedata, int slice, in
id<MTLCommandBuffer> cmd = gfx->getCommandBuffer(); id<MTLCommandBuffer> cmd = gfx->getCommandBuffer();
gfx->submitBlitEncoder(); gfx->submitBlitEncoder();
gfx->submitCommandBuffer(); gfx->submitCommandBuffer(Graphics::SUBMIT_STORE);
[cmd waitUntilCompleted]; [cmd waitUntilCompleted];