Update buffer size when it resizes mid-capture. Closes #2149

* We need to be careful with this, as we want to update the buffer's creation
  chunks without invalidating any data that may be present there.
This commit is contained in:
baldurk
2021-01-18 11:57:50 +00:00
parent 03db0c480e
commit 84dabc7b54
10 changed files with 310 additions and 8 deletions
+14
View File
@@ -149,6 +149,20 @@ INSTANTIATE_SERIALISE_TYPE(ResourceId);
// from image_viewer.cpp
ReplayStatus IMG_CreateReplayDevice(RDCFile *rdc, IReplayDriver **driver);
template <>
rdcstr DoStringise(const CaptureState &el)
{
BEGIN_ENUM_STRINGISE(CaptureState);
{
STRINGISE_ENUM_CLASS(LoadingReplaying);
STRINGISE_ENUM_CLASS(ActiveReplaying);
STRINGISE_ENUM_CLASS(StructuredExport);
STRINGISE_ENUM_CLASS(BackgroundCapturing);
STRINGISE_ENUM_CLASS(ActiveCapturing);
}
END_ENUM_STRINGISE();
}
template <>
rdcstr DoStringise(const RDCDriver &el)
{
+2
View File
@@ -124,6 +124,8 @@ enum class CaptureState
ActiveCapturing,
};
DECLARE_REFLECTION_ENUM(CaptureState);
constexpr inline bool IsReplayMode(CaptureState state)
{
return state == CaptureState::LoadingReplaying || state == CaptureState::ActiveReplaying;
+21
View File
@@ -2412,6 +2412,13 @@ bool WrappedOpenGL::EndFrameCapture(void *dev, void *wnd)
record->FreeShadowStorage();
}
for(const rdcpair<GLResourceRecord *, Chunk *> &r : m_BufferResizes)
{
r.first->AddChunk(r.second);
r.first->SetDataPtr(r.second->GetData());
}
m_BufferResizes.clear();
// if we changed contexts above, pop back to where we were
if(pushChildSaved)
{
@@ -2468,6 +2475,13 @@ bool WrappedOpenGL::EndFrameCapture(void *dev, void *wnd)
record->FreeShadowStorage();
}
for(const rdcpair<GLResourceRecord *, Chunk *> &r : m_BufferResizes)
{
r.first->AddChunk(r.second);
r.first->SetDataPtr(r.second->GetData());
}
m_BufferResizes.clear();
// if it's a capture triggered from application code, immediately
// give up as it's not reasonable to expect applications to detect and retry.
// otherwise we can retry in case the next frame works.
@@ -2542,6 +2556,13 @@ bool WrappedOpenGL::DiscardFrameCapture(void *dev, void *wnd)
record->FreeShadowStorage();
}
for(const rdcpair<GLResourceRecord *, Chunk *> &r : m_BufferResizes)
{
r.first->AddChunk(r.second);
r.first->SetDataPtr(r.second->GetData());
}
m_BufferResizes.clear();
m_CapturedFrames.pop_back();
FreeCaptureData();
+2
View File
@@ -187,6 +187,8 @@ private:
std::set<void *> m_AcceptedCtx;
rdcarray<rdcpair<GLResourceRecord *, Chunk *>> m_BufferResizes;
public:
enum
{
@@ -679,13 +679,15 @@ void WrappedOpenGL::glNamedBufferDataEXT(GLuint buffer, GLsizeiptr size, const v
return;
}
const bool isResizingOrphan =
(record->HasDataPtr() || (record->Length > 0 && size != (GLsizeiptr)record->Length));
// if we're recreating the buffer, clear the record and add new chunks. Normally
// we would just mark this record as dirty and pick it up on the capture frame as initial
// data, but we don't support (if it's even possible) querying out size etc.
// we need to add only the chunks required - glGenBuffers, glBindBuffer to current target,
// and this buffer storage. All other chunks have no effect
if(IsBackgroundCapturing(m_State) &&
(record->HasDataPtr() || (record->Length > 0 && size != (GLsizeiptr)record->Length)))
if(IsBackgroundCapturing(m_State) && isResizingOrphan)
{
// we need to maintain chunk ordering, so fetch the first two chunk IDs.
// We should have at least two by this point - glGenBuffers and whatever gave the record
@@ -753,15 +755,26 @@ void WrappedOpenGL::glNamedBufferDataEXT(GLuint buffer, GLsizeiptr size, const v
GetContextRecord()->AddChunk(chunk);
GetResourceManager()->MarkResourceFrameReferenced(record->GetResourceID(),
eFrameRef_PartialWrite);
// if this is a resizing call, we also need to store a copy in the record so future captures
// have an accurate creation chunk. However we can't do that yet because this buffer may not
// have initial contents. If we store the chunk immediately we'd corrupt data potentially used
// earlier in the captured frame from the previous creation chunk :(. So push it into a list
// that we'll 'apply' at the end of the frame capture.
if(isResizingOrphan)
m_BufferResizes.push_back({record, chunk->Duplicate()});
}
else
{
record->AddChunk(chunk);
record->SetDataPtr(chunk->GetData());
record->Length = (int32_t)size;
record->usage = usage;
record->DataInSerialiser = true;
}
// always update length and usage even during capture. If buffers resize mid-capture we'll
// record them both into the active frame and the record, but we need an up to date length.
record->Length = (int32_t)size;
record->usage = usage;
}
else
{
@@ -830,13 +843,15 @@ void WrappedOpenGL::glBufferData(GLenum target, GLsizeiptr size, const void *dat
GLuint buffer = record->Resource.name;
const bool isResizingOrphan =
(record->HasDataPtr() || (record->Length > 0 && size != (GLsizeiptr)record->Length));
// if we're recreating the buffer, clear the record and add new chunks. Normally
// we would just mark this record as dirty and pick it up on the capture frame as initial
// data, but we don't support (if it's even possible) querying out size etc.
// we need to add only the chunks required - glGenBuffers, glBindBuffer to current target,
// and this buffer storage. All other chunks have no effect
if(IsBackgroundCapturing(m_State) &&
(record->HasDataPtr() || (record->Length > 0 && size != (GLsizeiptr)record->Length)))
if(IsBackgroundCapturing(m_State) && isResizingOrphan)
{
// we need to maintain chunk ordering, so fetch the first two chunk IDs.
// We should have at least two by this point - glGenBuffers and whatever gave the record
@@ -904,13 +919,16 @@ void WrappedOpenGL::glBufferData(GLenum target, GLsizeiptr size, const void *dat
GetContextRecord()->AddChunk(chunk);
GetResourceManager()->MarkResourceFrameReferenced(record->GetResourceID(),
eFrameRef_PartialWrite);
// if this is a resizing call, also store a copy in the record so future captures have an
// accurate creation chunk
if(isResizingOrphan)
m_BufferResizes.push_back({record, chunk->Duplicate()});
}
else
{
record->AddChunk(chunk);
record->SetDataPtr(chunk->GetData());
record->Length = size;
record->usage = usage;
record->DataInSerialiser = true;
// if we're active capturing then we need to add a duplicate call in so that the data is
@@ -922,6 +940,9 @@ void WrappedOpenGL::glBufferData(GLenum target, GLsizeiptr size, const void *dat
eFrameRef_PartialWrite);
}
}
record->Length = size;
record->usage = usage;
}
else
{
+1
View File
@@ -59,6 +59,7 @@ set(OPENGL_SRC
3rdparty/glad/glad_glx.c
gl/gl_test.cpp
gl/gl_test_linux.cpp
gl/gl_buffer_resizing.cpp
gl/gl_buffer_spam.cpp
gl/gl_buffer_truncation.cpp
gl/gl_buffer_updates.cpp
+1
View File
@@ -217,6 +217,7 @@
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="3rdparty\glad\glad_wgl.c" />
<ClCompile Include="gl\gl_buffer_resizing.cpp" />
<ClCompile Include="gl\gl_buffer_spam.cpp" />
<ClCompile Include="gl\gl_buffer_truncation.cpp" />
<ClCompile Include="gl\gl_buffer_updates.cpp" />
+3
View File
@@ -571,6 +571,9 @@
<ClCompile Include="d3d11\d3d11_parameter_zoo.cpp">
<Filter>D3D11\demos</Filter>
</ClCompile>
<ClCompile Include="gl\gl_buffer_resizing.cpp">
<Filter>OpenGL\demos</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Filter Include="D3D11">
+181
View File
@@ -0,0 +1,181 @@
/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2019-2021 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#include "gl_test.h"
RD_TEST(GL_Buffer_Resizing, OpenGLGraphicsTest)
{
static constexpr const char *Description =
"Test that buffer resizing is handled correctly, both out of frame and in-frame.";
int main()
{
// initialise, create window, create context, etc
if(!Init())
return 3;
GLuint vao = MakeVAO();
glBindVertexArray(vao);
GLuint vbs[10];
int i = 0;
vbs[i] = MakeBuffer();
glBindBuffer(GL_ARRAY_BUFFER, vbs[i++]);
// create the buffer initially too small
glBufferData(GL_ARRAY_BUFFER, 4, NULL, GL_DYNAMIC_DRAW);
// then resize it up while at init time, to ensure we handle this correctly
glBufferData(GL_ARRAY_BUFFER, sizeof(DefaultTri), DefaultTri, GL_DYNAMIC_DRAW);
// while harmless, test that we can resize *down* as well.
vbs[i] = MakeBuffer();
glBindBuffer(GL_ARRAY_BUFFER, vbs[i++]);
glBufferData(GL_ARRAY_BUFFER, sizeof(DefaultTri) * 10, NULL, GL_DYNAMIC_DRAW);
glBufferData(GL_ARRAY_BUFFER, sizeof(DefaultTri), DefaultTri, GL_DYNAMIC_DRAW);
// these will be resized in-frame
vbs[i] = MakeBuffer();
glBindBuffer(GL_ARRAY_BUFFER, vbs[i++]);
glBufferData(GL_ARRAY_BUFFER, 4, NULL, GL_DYNAMIC_DRAW);
vbs[i] = MakeBuffer();
glBindBuffer(GL_ARRAY_BUFFER, vbs[i++]);
glBufferData(GL_ARRAY_BUFFER, sizeof(DefaultTri) * 10, NULL, GL_DYNAMIC_DRAW);
vbs[i] = MakeBuffer();
glBindBuffer(GL_ARRAY_BUFFER, vbs[i++]);
glBufferData(GL_ARRAY_BUFFER, 4, NULL, GL_DYNAMIC_DRAW);
vbs[i] = MakeBuffer();
glBindBuffer(GL_ARRAY_BUFFER, vbs[i++]);
glBufferData(GL_ARRAY_BUFFER, 1000, NULL, GL_DYNAMIC_DRAW);
vbs[i] = MakeBuffer();
glBindBuffer(GL_ARRAY_BUFFER, vbs[i++]);
glBufferData(GL_ARRAY_BUFFER, 4, NULL, GL_DYNAMIC_DRAW);
TEST_ASSERT(i < ARRAY_COUNT(vbs), "Make vbs[] bigger");
GLuint program = MakeProgram(GLDefaultVertex, GLDefaultPixel);
glUseProgram(program);
glViewport(0, 0, GLsizei(screenWidth), GLsizei(screenHeight));
float col[] = {0.2f, 0.2f, 0.2f, 1.0f};
while(Running())
{
i = 0;
// check these VBs are OK
glClearBufferfv(GL_COLOR, 0, col);
glBindBuffer(GL_ARRAY_BUFFER, vbs[i++]);
ConfigureDefaultVAO();
glDrawArrays(GL_TRIANGLES, 0, 3);
// check these VBs are OK
glClearBufferfv(GL_COLOR, 0, col);
glBindBuffer(GL_ARRAY_BUFFER, vbs[i++]);
ConfigureDefaultVAO();
glDrawArrays(GL_TRIANGLES, 0, 3);
if(curFrame == 10)
{
// resize this VB up to size in the captured frame
glClearBufferfv(GL_COLOR, 0, col);
glBindBuffer(GL_ARRAY_BUFFER, vbs[i++]);
glBufferData(GL_ARRAY_BUFFER, sizeof(DefaultTri), DefaultTri, GL_DYNAMIC_DRAW);
ConfigureDefaultVAO();
if(glGetError() == GL_NO_ERROR)
glDrawArrays(GL_TRIANGLES, 0, 3);
else
glDrawArrays(GL_TRIANGLES, 0, 0);
// resize this VB down to size in the captured frame
glClearBufferfv(GL_COLOR, 0, col);
glBindBuffer(GL_ARRAY_BUFFER, vbs[i++]);
glBufferData(GL_ARRAY_BUFFER, sizeof(DefaultTri), DefaultTri, GL_DYNAMIC_DRAW);
ConfigureDefaultVAO();
if(glGetError() == GL_NO_ERROR)
glDrawArrays(GL_TRIANGLES, 0, 3);
else
glDrawArrays(GL_TRIANGLES, 0, 0);
// resize this VB several times in the captured frame
glClearBufferfv(GL_COLOR, 0, col);
glBindBuffer(GL_ARRAY_BUFFER, vbs[i++]);
glBufferData(GL_ARRAY_BUFFER, 16, NULL, GL_DYNAMIC_DRAW);
glBufferData(GL_ARRAY_BUFFER, 8, NULL, GL_DYNAMIC_DRAW);
glBufferData(GL_ARRAY_BUFFER, 8, NULL, GL_DYNAMIC_DRAW);
glBufferData(GL_ARRAY_BUFFER, 9999, NULL, GL_DYNAMIC_DRAW);
glBufferData(GL_ARRAY_BUFFER, sizeof(DefaultTri), DefaultTri, GL_DYNAMIC_DRAW);
ConfigureDefaultVAO();
if(glGetError() == GL_NO_ERROR)
glDrawArrays(GL_TRIANGLES, 0, 3);
else
glDrawArrays(GL_TRIANGLES, 0, 0);
// resize down and map this VB
glClearBufferfv(GL_COLOR, 0, col);
glBindBuffer(GL_ARRAY_BUFFER, vbs[i++]);
glBufferData(GL_ARRAY_BUFFER, sizeof(DefaultTri), NULL, GL_DYNAMIC_DRAW);
void *ptr = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
memcpy(ptr, DefaultTri, sizeof(DefaultTri));
glUnmapBuffer(GL_ARRAY_BUFFER);
ConfigureDefaultVAO();
if(glGetError() == GL_NO_ERROR)
glDrawArrays(GL_TRIANGLES, 0, 3);
else
glDrawArrays(GL_TRIANGLES, 0, 0);
// resize up and map this VB
glClearBufferfv(GL_COLOR, 0, col);
glBindBuffer(GL_ARRAY_BUFFER, vbs[i++]);
glBufferData(GL_ARRAY_BUFFER, sizeof(DefaultTri), NULL, GL_DYNAMIC_DRAW);
ptr = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
memcpy(ptr, DefaultTri, sizeof(DefaultTri));
glUnmapBuffer(GL_ARRAY_BUFFER);
ConfigureDefaultVAO();
if(glGetError() == GL_NO_ERROR)
glDrawArrays(GL_TRIANGLES, 0, 3);
else
glDrawArrays(GL_TRIANGLES, 0, 0);
// now trash the VBs that had important data at the start of the frame, to ensure that this
// resize doesn't invalid any of the data that was in them and used.
glBindBuffer(GL_ARRAY_BUFFER, vbs[0]);
glBufferData(GL_ARRAY_BUFFER, 50, NULL, GL_DYNAMIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, vbs[1]);
glBufferData(GL_ARRAY_BUFFER, 50, NULL, GL_DYNAMIC_DRAW);
}
Present();
}
return 0;
}
};
REGISTER_TEST();
+56
View File
@@ -0,0 +1,56 @@
import renderdoc as rd
import rdtest
class GL_Buffer_Resizing(rdtest.TestCase):
demos_test_name = 'GL_Buffer_Resizing'
demos_frame_cap = 10
def check_capture(self):
postvs_ref = {
0: {
'vtx': 0,
'idx': 0,
'gl_Position': [-0.5, -0.5, 0.0, 1.0],
'v2f_block.pos': [-0.5, -0.5, 0.0, 1.0],
'v2f_block.col': [0.0, 1.0, 0.0, 1.0],
'v2f_block.uv': [0.0, 0.0, 0.0, 1.0],
},
1: {
'vtx': 1,
'idx': 1,
'gl_Position': [0.0, 0.5, 0.0, 1.0],
'v2f_block.pos': [0.0, 0.5, 0.0, 1.0],
'v2f_block.col': [0.0, 1.0, 0.0, 1.0],
'v2f_block.uv': [0.0, 1.0, 0.0, 1.0],
},
2: {
'vtx': 2,
'idx': 2,
'gl_Position': [0.5, -0.5, 0.0, 1.0],
'v2f_block.pos': [0.5, -0.5, 0.0, 1.0],
'v2f_block.col': [0.0, 1.0, 0.0, 1.0],
'v2f_block.uv': [1.0, 0.0, 0.0, 1.0],
},
}
draw = self.get_first_draw()
idx = 0
while True:
draw: rd.DrawcallDescription = self.find_draw('glDraw', draw.eventId+1)
if draw is None:
break
self.controller.SetFrameEvent(draw.eventId, True)
self.check_triangle(out=draw.outputs[0])
postvs_data = self.get_postvs(draw, rd.MeshDataStage.VSOut, 0, draw.numIndices)
self.check_mesh_data(postvs_ref, postvs_data)
idx = idx + 1
rdtest.log.success('Draw {} at {} is correct'.format(idx, draw.eventId))