Files
renderdoc/renderdoc/driver/gl/gl_initstate.cpp
T

2302 lines
84 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2017 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 <algorithm>
#include "gl_driver.h"
#include "gl_manager.h"
struct VertexAttribInitialData
{
uint32_t enabled;
uint32_t vbslot;
uint32_t offset;
GLenum type;
int32_t normalized;
uint32_t integer;
uint32_t size;
};
DECLARE_REFLECTION_STRUCT(VertexAttribInitialData);
struct VertexBufferInitialData
{
GLResource Buffer;
uint64_t Stride;
uint64_t Offset;
uint32_t Divisor;
};
DECLARE_REFLECTION_STRUCT(VertexBufferInitialData);
// note these data structures below contain a 'valid' bool, since due to complexities of
// fetching the state on the right context, we might never be able to fetch the data at
// all. So the valid is set to false to indicate that we shouldn't try to restore it on
// replay.
struct VAOInitialData
{
bool valid;
VertexAttribInitialData VertexAttribs[16];
VertexBufferInitialData VertexBuffers[16];
GLResource ElementArrayBuffer;
};
DECLARE_REFLECTION_STRUCT(VAOInitialData);
struct FeedbackInitialData
{
bool valid;
GLResource Buffer[4];
uint64_t Offset[4];
uint64_t Size[4];
};
DECLARE_REFLECTION_STRUCT(FeedbackInitialData);
struct FramebufferAttachmentData
{
bool layered;
int32_t layer;
int32_t level;
GLResource obj;
};
DECLARE_REFLECTION_STRUCT(FramebufferAttachmentData);
struct FramebufferInitialData
{
bool valid;
FramebufferAttachmentData Attachments[10];
GLenum DrawBuffers[8];
GLenum ReadBuffer;
static const GLenum attachmentNames[10];
};
DECLARE_REFLECTION_STRUCT(FramebufferInitialData);
const GLenum FramebufferInitialData::attachmentNames[10] = {
eGL_COLOR_ATTACHMENT0, eGL_COLOR_ATTACHMENT1, eGL_COLOR_ATTACHMENT2, eGL_COLOR_ATTACHMENT3,
eGL_COLOR_ATTACHMENT4, eGL_COLOR_ATTACHMENT5, eGL_COLOR_ATTACHMENT6, eGL_COLOR_ATTACHMENT7,
eGL_DEPTH_ATTACHMENT, eGL_STENCIL_ATTACHMENT,
};
struct PipelineInitialData
{
bool valid;
GLResource programs[6];
};
DECLARE_REFLECTION_STRUCT(PipelineInitialData);
struct TextureStateInitialData
{
// these are slightly redundant but convenient to have with the initial state data.
GLenum internalformat;
bool isView;
uint32_t width, height, depth;
uint32_t samples;
uint32_t dim;
GLenum type;
int mips;
int32_t baseLevel, maxLevel;
float minLod, maxLod;
GLenum srgbDecode;
GLenum depthMode;
GLenum compareFunc, compareMode;
GLenum minFilter, magFilter;
int32_t seamless;
GLenum swizzle[4];
GLenum wrap[3];
float border[4];
float lodBias;
GLResource texBuffer;
uint32_t texBufOffs;
uint32_t texBufSize;
};
DECLARE_REFLECTION_STRUCT(TextureStateInitialData);
template <typename SerialiserType>
void DoSerialise(SerialiserType &ser, VertexAttribInitialData &el)
{
SERIALISE_MEMBER(enabled);
SERIALISE_MEMBER(vbslot);
SERIALISE_MEMBER(offset);
SERIALISE_MEMBER(type);
SERIALISE_MEMBER(normalized);
SERIALISE_MEMBER(integer);
SERIALISE_MEMBER(size);
}
template <typename SerialiserType>
void DoSerialise(SerialiserType &ser, VertexBufferInitialData &el)
{
SERIALISE_MEMBER(Buffer);
SERIALISE_MEMBER(Stride);
SERIALISE_MEMBER(Offset);
SERIALISE_MEMBER(Divisor);
}
template <typename SerialiserType>
void DoSerialise(SerialiserType &ser, VAOInitialData &el)
{
SERIALISE_MEMBER(valid);
SERIALISE_MEMBER(VertexAttribs);
SERIALISE_MEMBER(VertexBuffers);
SERIALISE_MEMBER(ElementArrayBuffer);
}
template <typename SerialiserType>
void DoSerialise(SerialiserType &ser, FeedbackInitialData &el)
{
SERIALISE_MEMBER(valid);
SERIALISE_MEMBER(Buffer);
SERIALISE_MEMBER(Offset);
SERIALISE_MEMBER(Size);
}
template <typename SerialiserType>
void DoSerialise(SerialiserType &ser, FramebufferAttachmentData &el)
{
SERIALISE_MEMBER(layered);
SERIALISE_MEMBER(layer);
SERIALISE_MEMBER(level);
SERIALISE_MEMBER(obj);
}
template <typename SerialiserType>
void DoSerialise(SerialiserType &ser, FramebufferInitialData &el)
{
SERIALISE_MEMBER(valid);
SERIALISE_MEMBER(DrawBuffers);
SERIALISE_MEMBER(Attachments);
SERIALISE_MEMBER(ReadBuffer);
}
template <typename SerialiserType>
void DoSerialise(SerialiserType &ser, PipelineInitialData &el)
{
SERIALISE_MEMBER(valid);
SERIALISE_MEMBER(programs);
}
template <typename SerialiserType>
void DoSerialise(SerialiserType &ser, TextureStateInitialData &el)
{
SERIALISE_MEMBER(internalformat);
SERIALISE_MEMBER(isView);
SERIALISE_MEMBER(width);
SERIALISE_MEMBER(height);
SERIALISE_MEMBER(depth);
SERIALISE_MEMBER(samples);
SERIALISE_MEMBER(dim);
SERIALISE_MEMBER(type);
SERIALISE_MEMBER(mips);
SERIALISE_MEMBER(baseLevel);
SERIALISE_MEMBER(maxLevel);
SERIALISE_MEMBER(minLod);
SERIALISE_MEMBER(maxLod);
SERIALISE_MEMBER(srgbDecode);
SERIALISE_MEMBER(depthMode);
SERIALISE_MEMBER(compareFunc);
SERIALISE_MEMBER(compareMode);
SERIALISE_MEMBER(seamless);
SERIALISE_MEMBER(minFilter);
SERIALISE_MEMBER(magFilter);
SERIALISE_MEMBER(swizzle);
SERIALISE_MEMBER(wrap);
SERIALISE_MEMBER(border);
SERIALISE_MEMBER(lodBias);
SERIALISE_MEMBER(texBuffer);
SERIALISE_MEMBER(texBufOffs);
SERIALISE_MEMBER(texBufSize);
}
void WrappedOpenGL::TextureData::GetCompressedImageDataGLES(int mip, GLenum target, size_t size,
byte *buf)
{
const vector<byte> &data = compressedData[mip];
memset(buf, 0, size);
size_t startOffs = IsCubeFace(target) ? CubeTargetIndex(target) * size : 0;
if(data.size() >= startOffs)
{
size_t byteSize = RDCMIN(data.size() - startOffs, size);
if(byteSize > 0)
memcpy(buf, data.data() + startOffs, byteSize);
}
}
bool GLResourceManager::Need_InitialStateChunk(GLResource res)
{
return true;
}
bool GLResourceManager::Prepare_InitialState(GLResource res, byte *blob)
{
const GLHookSet &gl = m_GL->GetHookset();
if(res.Namespace == eResFramebuffer)
{
FramebufferInitialData *data = (FramebufferInitialData *)blob;
data->valid = true;
GLuint prevread = 0, prevdraw = 0;
gl.glGetIntegerv(eGL_DRAW_FRAMEBUFFER_BINDING, (GLint *)&prevdraw);
gl.glGetIntegerv(eGL_READ_FRAMEBUFFER_BINDING, (GLint *)&prevread);
gl.glBindFramebuffer(eGL_DRAW_FRAMEBUFFER, res.name);
gl.glBindFramebuffer(eGL_READ_FRAMEBUFFER, res.name);
// need to serialise out which objects are bound
GLenum type = eGL_TEXTURE;
GLuint object = 0;
GLint layered = 0;
for(int i = 0; i < (int)ARRAY_COUNT(data->Attachments); i++)
{
FramebufferAttachmentData &a = data->Attachments[i];
GLenum attachment = FramebufferInitialData::attachmentNames[i];
gl.glGetNamedFramebufferAttachmentParameterivEXT(
res.name, attachment, eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, (GLint *)&object);
gl.glGetNamedFramebufferAttachmentParameterivEXT(
res.name, attachment, eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, (GLint *)&type);
layered = 0;
a.level = 0;
a.layer = 0;
if(object && type != eGL_RENDERBUFFER)
{
gl.glGetNamedFramebufferAttachmentParameterivEXT(
res.name, attachment, eGL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, &a.level);
gl.glGetNamedFramebufferAttachmentParameterivEXT(
res.name, attachment, eGL_FRAMEBUFFER_ATTACHMENT_LAYERED, &layered);
if(layered == 0)
gl.glGetNamedFramebufferAttachmentParameterivEXT(
res.name, attachment, eGL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER, &a.layer);
}
a.layered = (layered != 0);
a.obj = (type == eGL_RENDERBUFFER) ? RenderbufferRes(res.Context, object)
: TextureRes(res.Context, object);
if(type != eGL_RENDERBUFFER)
{
WrappedOpenGL::TextureData &details = m_GL->m_Textures[GetID(a.obj)];
if(details.curType == eGL_TEXTURE_CUBE_MAP)
{
GLenum face;
gl.glGetNamedFramebufferAttachmentParameterivEXT(
res.name, attachment, eGL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE, (GLint *)&face);
a.layer = CubeTargetIndex(face);
}
}
}
for(int i = 0; i < (int)ARRAY_COUNT(data->DrawBuffers); i++)
gl.glGetIntegerv(GLenum(eGL_DRAW_BUFFER0 + i), (GLint *)&data->DrawBuffers[i]);
gl.glGetIntegerv(eGL_READ_BUFFER, (GLint *)&data->ReadBuffer);
gl.glBindFramebuffer(eGL_DRAW_FRAMEBUFFER, prevdraw);
gl.glBindFramebuffer(eGL_READ_FRAMEBUFFER, prevread);
}
else if(res.Namespace == eResProgramPipe)
{
PipelineInitialData *data = (PipelineInitialData *)blob;
data->valid = true;
#if ENABLED(RDOC_DEVEL)
if(ProgramRes(m_GL->GetCtx(), 0).Context != NULL)
RDCFATAL("Program resources are context-specific - we assume they aren't");
#endif
for(GLuint i = 0; i < (GLuint)ARRAY_COUNT(data->programs); i++)
data->programs[i].Namespace = eResProgram;
gl.glGetProgramPipelineiv(res.name, eGL_VERTEX_SHADER, (GLint *)&data->programs[0].name);
gl.glGetProgramPipelineiv(res.name, eGL_FRAGMENT_SHADER, (GLint *)&data->programs[4].name);
gl.glGetProgramPipelineiv(res.name, eGL_GEOMETRY_SHADER, (GLint *)&data->programs[3].name);
gl.glGetProgramPipelineiv(res.name, eGL_TESS_CONTROL_SHADER, (GLint *)&data->programs[1].name);
gl.glGetProgramPipelineiv(res.name, eGL_TESS_EVALUATION_SHADER, (GLint *)&data->programs[2].name);
gl.glGetProgramPipelineiv(res.name, eGL_COMPUTE_SHADER, (GLint *)&data->programs[5].name);
}
else if(res.Namespace == eResFeedback)
{
FeedbackInitialData *data = (FeedbackInitialData *)blob;
data->valid = true;
GLuint prevfeedback = 0;
gl.glGetIntegerv(eGL_TRANSFORM_FEEDBACK_BINDING, (GLint *)&prevfeedback);
gl.glBindTransformFeedback(eGL_TRANSFORM_FEEDBACK, res.name);
GLint maxCount = 0;
gl.glGetIntegerv(eGL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS, &maxCount);
for(int i = 0; i < (int)ARRAY_COUNT(data->Buffer) && i < maxCount; i++)
{
GLuint buffer = 0;
gl.glGetIntegeri_v(eGL_TRANSFORM_FEEDBACK_BUFFER_BINDING, i, (GLint *)&buffer);
data->Buffer[i] = BufferRes(res.Context, buffer);
gl.glGetInteger64i_v(eGL_TRANSFORM_FEEDBACK_BUFFER_START, i, (GLint64 *)&data->Offset[i]);
gl.glGetInteger64i_v(eGL_TRANSFORM_FEEDBACK_BUFFER_SIZE, i, (GLint64 *)&data->Size[i]);
}
gl.glBindTransformFeedback(eGL_TRANSFORM_FEEDBACK, prevfeedback);
}
else if(res.Namespace == eResVertexArray)
{
VAOInitialData *data = (VAOInitialData *)blob;
data->valid = true;
GLuint prevVAO = 0;
gl.glGetIntegerv(eGL_VERTEX_ARRAY_BINDING, (GLint *)&prevVAO);
if(res.name == 0)
gl.glBindVertexArray(m_GL->GetFakeVAO());
else
gl.glBindVertexArray(res.name);
for(GLuint i = 0; i < 16; i++)
{
gl.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_ARRAY_ENABLED,
(GLint *)&data->VertexAttribs[i].enabled);
gl.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_BINDING, (GLint *)&data->VertexAttribs[i].vbslot);
gl.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_RELATIVE_OFFSET,
(GLint *)&data->VertexAttribs[i].offset);
gl.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_ARRAY_TYPE, (GLint *)&data->VertexAttribs[i].type);
gl.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_ARRAY_NORMALIZED,
(GLint *)&data->VertexAttribs[i].normalized);
gl.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_ARRAY_INTEGER,
(GLint *)&data->VertexAttribs[i].integer);
gl.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_ARRAY_SIZE, (GLint *)&data->VertexAttribs[i].size);
GLuint buffer = GetBoundVertexBuffer(gl, i);
data->VertexBuffers[i].Buffer = BufferRes(res.Context, buffer);
gl.glGetIntegeri_v(eGL_VERTEX_BINDING_STRIDE, i, (GLint *)&data->VertexBuffers[i].Stride);
gl.glGetIntegeri_v(eGL_VERTEX_BINDING_OFFSET, i, (GLint *)&data->VertexBuffers[i].Offset);
gl.glGetIntegeri_v(eGL_VERTEX_BINDING_DIVISOR, i, (GLint *)&data->VertexBuffers[i].Divisor);
}
GLuint buffer = 0;
gl.glGetIntegerv(eGL_ELEMENT_ARRAY_BUFFER_BINDING, (GLint *)&buffer);
data->ElementArrayBuffer = BufferRes(res.Context, buffer);
gl.glBindVertexArray(prevVAO);
}
return true;
}
bool GLResourceManager::Prepare_InitialState(GLResource res)
{
// this function needs to be refactored to better deal with multiple
// contexts and resources that are specific to a particular context
ResourceId Id = GetID(res);
const GLHookSet &gl = m_GL->GetHookset();
if(res.Namespace == eResBuffer)
{
// get the length of the buffer
uint32_t length = 1;
gl.glGetNamedBufferParameterivEXT(res.name, eGL_BUFFER_SIZE, (GLint *)&length);
// save old bindings
GLuint oldbuf1 = 0, oldbuf2 = 0;
gl.glGetIntegerv(eGL_COPY_READ_BUFFER_BINDING, (GLint *)&oldbuf1);
gl.glGetIntegerv(eGL_COPY_WRITE_BUFFER_BINDING, (GLint *)&oldbuf2);
// create a new buffer big enough to hold the contents
GLuint buf = 0;
gl.glGenBuffers(1, &buf);
gl.glBindBuffer(eGL_COPY_WRITE_BUFFER, buf);
gl.glNamedBufferDataEXT(buf, (GLsizeiptr)length, NULL, eGL_STATIC_READ);
// bind the live buffer for copying
gl.glBindBuffer(eGL_COPY_READ_BUFFER, res.name);
// do the actual copy
gl.glCopyBufferSubData(eGL_COPY_READ_BUFFER, eGL_COPY_WRITE_BUFFER, 0, 0, (GLsizeiptr)length);
// restore old bindings
gl.glBindBuffer(eGL_COPY_READ_BUFFER, oldbuf1);
gl.glBindBuffer(eGL_COPY_WRITE_BUFFER, oldbuf2);
SetInitialContents(
Id, InitialContentData(res.Namespace, BufferRes(res.Context, buf), length, NULL));
}
else if(res.Namespace == eResProgram)
{
WriteSerialiser ser(new StreamWriter(4 * 1024), Ownership::Stream);
ser.SetChunkMetadataRecording(m_GL->GetSerialiser().GetChunkMetadataRecording());
SCOPED_SERIALISE_CHUNK(SystemChunk::InitialContents);
SERIALISE_ELEMENT(Id);
SERIALISE_ELEMENT(res.Namespace);
SerialiseProgramBindings(ser, CaptureState::ActiveCapturing, gl, res.name);
SerialiseProgramUniforms(ser, CaptureState::ActiveCapturing, gl, res.name, NULL);
SetInitialChunk(Id, scope.Get());
}
else if(res.Namespace == eResTexture)
{
PrepareTextureInitialContents(Id, Id, res);
}
else if(res.Namespace == eResFramebuffer)
{
byte *data = AllocAlignedBuffer(sizeof(FramebufferInitialData));
RDCEraseMem(data, sizeof(FramebufferInitialData));
SetInitialContents(Id, InitialContentData(res.Namespace, GLResource(MakeNullResource), 0, data));
// if FBOs aren't shared we need to fetch the data for this FBO on the right context. It's
// not safe for us to go changing contexts ourselves (the context could be active on another
// thread), so instead we'll queue this up to fetch when we are on the correct context.
//
// Because we've already allocated and set the blob above, it can be filled in any time
// before serialising (end of the frame, and if the context is never used before the end of
// the frame the resource can't be used, so not fetching the initial state doesn't matter).
//
// Note we also need to detect the case where the context is already current on another thread
// and we just start getting commands there, but that case already isn't supported as we don't
// detect it and insert state-change chunks, we assume all commands will come from a single
// thread.
if(!VendorCheck[VendorCheck_EXT_fbo_shared] && res.Context && m_GL->GetCtx() != res.Context)
{
m_GL->QueuePrepareInitialState(res, data);
}
else
{
// call immediately, we are on the right context or for one reason or another the context
// doesn't matter for fetching this resource (res.Context is NULL or vendorcheck means they're
// shared).
Prepare_InitialState(res, (byte *)data);
}
}
else if(res.Namespace == eResProgramPipe)
{
byte *data = AllocAlignedBuffer(sizeof(PipelineInitialData));
RDCEraseMem(data, sizeof(PipelineInitialData));
SetInitialContents(Id, InitialContentData(res.Namespace, GLResource(MakeNullResource), 0, data));
// queue initial state fetching if we're not on the right context, see above in FBOs for more
// explanation of this.
if(res.Context && m_GL->GetCtx() != res.Context)
{
m_GL->QueuePrepareInitialState(res, data);
}
else
{
Prepare_InitialState(res, (byte *)data);
}
}
else if(res.Namespace == eResFeedback)
{
byte *data = AllocAlignedBuffer(sizeof(FeedbackInitialData));
RDCEraseMem(data, sizeof(FeedbackInitialData));
SetInitialContents(Id, InitialContentData(res.Namespace, GLResource(MakeNullResource), 0, data));
// queue initial state fetching if we're not on the right context, see above in FBOs for more
// explanation of this.
if(res.Context && m_GL->GetCtx() != res.Context)
{
m_GL->QueuePrepareInitialState(res, data);
}
else
{
Prepare_InitialState(res, (byte *)data);
}
}
else if(res.Namespace == eResVertexArray)
{
byte *data = AllocAlignedBuffer(sizeof(VAOInitialData));
RDCEraseMem(data, sizeof(VAOInitialData));
SetInitialContents(Id, InitialContentData(res.Namespace, GLResource(MakeNullResource), 0, data));
// queue initial state fetching if we're not on the right context, see above in FBOs for more
// explanation of this.
if(res.Context && m_GL->GetCtx() != res.Context)
{
m_GL->QueuePrepareInitialState(res, data);
}
else
{
Prepare_InitialState(res, (byte *)data);
}
}
else if(res.Namespace == eResRenderbuffer)
{
//
}
else
{
RDCERR("Unexpected type of resource requiring initial state");
}
return true;
}
void GLResourceManager::CreateTextureImage(GLuint tex, GLenum internalFormat, GLenum textype,
GLint dim, GLint width, GLint height, GLint depth,
GLint samples, int mips)
{
const GLHookSet &gl = m_GL->GetHookset();
if(textype == eGL_TEXTURE_BUFFER)
{
return;
}
else if(textype == eGL_TEXTURE_2D_MULTISAMPLE)
{
gl.glTextureStorage2DMultisampleEXT(tex, textype, samples, internalFormat, width, height,
GL_TRUE);
}
else if(textype == eGL_TEXTURE_2D_MULTISAMPLE_ARRAY)
{
gl.glTextureStorage3DMultisampleEXT(tex, textype, samples, internalFormat, width, height, depth,
GL_TRUE);
}
else
{
gl.glTextureParameteriEXT(tex, textype, eGL_TEXTURE_MAX_LEVEL, mips - 1);
bool isCompressed = IsCompressedFormat(internalFormat);
GLenum baseFormat = eGL_RGBA;
GLenum dataType = eGL_UNSIGNED_BYTE;
if(!isCompressed)
{
baseFormat = GetBaseFormat(internalFormat);
dataType = GetDataType(internalFormat);
}
GLenum targets[] = {
eGL_TEXTURE_CUBE_MAP_POSITIVE_X, eGL_TEXTURE_CUBE_MAP_NEGATIVE_X,
eGL_TEXTURE_CUBE_MAP_POSITIVE_Y, eGL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
eGL_TEXTURE_CUBE_MAP_POSITIVE_Z, eGL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
};
int count = ARRAY_COUNT(targets);
if(textype != eGL_TEXTURE_CUBE_MAP)
{
targets[0] = textype;
count = 1;
}
GLsizei w = (GLsizei)width;
GLsizei h = (GLsizei)height;
GLsizei d = (GLsizei)depth;
for(int m = 0; m < mips; m++)
{
for(int t = 0; t < count; t++)
{
if(isCompressed)
{
GLsizei compSize = (GLsizei)GetCompressedByteSize(w, h, d, internalFormat);
vector<byte> dummy;
dummy.resize(compSize);
if(dim == 1)
gl.glCompressedTextureImage1DEXT(tex, targets[t], m, internalFormat, w, 0, compSize,
&dummy[0]);
else if(dim == 2)
gl.glCompressedTextureImage2DEXT(tex, targets[t], m, internalFormat, w, h, 0, compSize,
&dummy[0]);
else if(dim == 3)
gl.glCompressedTextureImage3DEXT(tex, targets[t], m, internalFormat, w, h, d, 0,
compSize, &dummy[0]);
}
else
{
if(dim == 1)
gl.glTextureImage1DEXT(tex, targets[t], m, internalFormat, w, 0, baseFormat, dataType,
NULL);
else if(dim == 2)
gl.glTextureImage2DEXT(tex, targets[t], m, internalFormat, w, h, 0, baseFormat,
dataType, NULL);
else if(dim == 3)
gl.glTextureImage3DEXT(tex, targets[t], m, internalFormat, w, h, d, 0, baseFormat,
dataType, NULL);
}
}
w = RDCMAX(1, w >> 1);
if(textype != eGL_TEXTURE_1D_ARRAY)
h = RDCMAX(1, h >> 1);
if(textype != eGL_TEXTURE_2D_ARRAY && textype != eGL_TEXTURE_CUBE_MAP_ARRAY)
d = RDCMAX(1, d >> 1);
}
}
}
void GLResourceManager::PrepareTextureInitialContents(ResourceId liveid, ResourceId origid,
GLResource res)
{
const GLHookSet &gl = m_GL->GetHookset();
WrappedOpenGL::TextureData &details = m_GL->m_Textures[liveid];
TextureStateInitialData *state =
(TextureStateInitialData *)AllocAlignedBuffer(sizeof(TextureStateInitialData));
RDCEraseMem(state, sizeof(TextureStateInitialData));
state->internalformat = details.internalFormat;
state->isView = details.view;
state->width = details.width;
state->height = details.height;
state->depth = details.depth;
state->samples = details.samples;
state->dim = details.dimension;
state->type = details.curType;
state->mips = 1;
if(details.internalFormat == eGL_NONE)
{
// textures can get here as GL_NONE if they were created and dirtied (by setting lots of
// texture parameters) without ever having storage allocated (via glTexStorage or glTexImage).
// in that case, just ignore as we won't bother with the initial states.
SetInitialContents(
origid, InitialContentData(res.Namespace, GLResource(MakeNullResource), 0, (byte *)state));
}
else if(details.curType != eGL_TEXTURE_BUFFER)
{
GLenum binding = TextureBinding(details.curType);
state->mips =
GetNumMips(gl, details.curType, res.name, details.width, details.height, details.depth);
bool ms = (details.curType == eGL_TEXTURE_2D_MULTISAMPLE ||
details.curType == eGL_TEXTURE_2D_MULTISAMPLE_ARRAY);
state->depthMode = eGL_NONE;
if(IsDepthStencilFormat(details.internalFormat))
{
if(HasExt[ARB_stencil_texturing])
gl.glGetTextureParameterivEXT(res.name, details.curType, eGL_DEPTH_STENCIL_TEXTURE_MODE,
(GLint *)&state->depthMode);
else
state->depthMode = eGL_DEPTH_COMPONENT;
}
state->seamless = GL_FALSE;
if((details.curType == eGL_TEXTURE_CUBE_MAP || details.curType == eGL_TEXTURE_CUBE_MAP_ARRAY) &&
HasExt[ARB_seamless_cubemap_per_texture])
gl.glGetTextureParameterivEXT(res.name, details.curType, eGL_TEXTURE_CUBE_MAP_SEAMLESS,
(GLint *)&state->seamless);
gl.glGetTextureParameterivEXT(res.name, details.curType, eGL_TEXTURE_BASE_LEVEL,
(GLint *)&state->baseLevel);
gl.glGetTextureParameterivEXT(res.name, details.curType, eGL_TEXTURE_MAX_LEVEL,
(GLint *)&state->maxLevel);
if(HasExt[ARB_texture_swizzle] || HasExt[EXT_texture_swizzle])
{
GetTextureSwizzle(gl, res.name, details.curType, state->swizzle);
}
else
{
state->swizzle[0] = eGL_RED;
state->swizzle[1] = eGL_GREEN;
state->swizzle[2] = eGL_BLUE;
state->swizzle[3] = eGL_ALPHA;
}
// only non-ms textures have sampler state
if(!ms)
{
gl.glGetTextureParameterivEXT(res.name, details.curType, eGL_TEXTURE_SRGB_DECODE_EXT,
(GLint *)&state->srgbDecode);
gl.glGetTextureParameterivEXT(res.name, details.curType, eGL_TEXTURE_COMPARE_FUNC,
(GLint *)&state->compareFunc);
gl.glGetTextureParameterivEXT(res.name, details.curType, eGL_TEXTURE_COMPARE_MODE,
(GLint *)&state->compareMode);
gl.glGetTextureParameterivEXT(res.name, details.curType, eGL_TEXTURE_MIN_FILTER,
(GLint *)&state->minFilter);
gl.glGetTextureParameterivEXT(res.name, details.curType, eGL_TEXTURE_MAG_FILTER,
(GLint *)&state->magFilter);
gl.glGetTextureParameterivEXT(res.name, details.curType, eGL_TEXTURE_WRAP_R,
(GLint *)&state->wrap[0]);
gl.glGetTextureParameterivEXT(res.name, details.curType, eGL_TEXTURE_WRAP_S,
(GLint *)&state->wrap[1]);
gl.glGetTextureParameterivEXT(res.name, details.curType, eGL_TEXTURE_WRAP_T,
(GLint *)&state->wrap[2]);
gl.glGetTextureParameterfvEXT(res.name, details.curType, eGL_TEXTURE_MIN_LOD, &state->minLod);
gl.glGetTextureParameterfvEXT(res.name, details.curType, eGL_TEXTURE_MAX_LOD, &state->maxLod);
gl.glGetTextureParameterfvEXT(res.name, details.curType, eGL_TEXTURE_BORDER_COLOR,
&state->border[0]);
if(!IsGLES)
gl.glGetTextureParameterfvEXT(res.name, details.curType, eGL_TEXTURE_LOD_BIAS,
&state->lodBias);
// CLAMP isn't supported (border texels gone), assume they meant CLAMP_TO_EDGE
if(state->wrap[0] == eGL_CLAMP)
state->wrap[0] = eGL_CLAMP_TO_EDGE;
if(state->wrap[1] == eGL_CLAMP)
state->wrap[1] = eGL_CLAMP_TO_EDGE;
if(state->wrap[2] == eGL_CLAMP)
state->wrap[2] = eGL_CLAMP_TO_EDGE;
}
// we only copy contents for non-views
GLuint tex = 0;
if(!details.view)
{
{
GLuint oldtex = 0;
gl.glGetIntegerv(binding, (GLint *)&oldtex);
gl.glGenTextures(1, &tex);
gl.glBindTexture(details.curType, tex);
gl.glBindTexture(details.curType, oldtex);
}
int mips =
GetNumMips(gl, details.curType, res.name, details.width, details.height, details.depth);
if(details.curType == eGL_TEXTURE_2D_MULTISAMPLE ||
details.curType == eGL_TEXTURE_2D_MULTISAMPLE_ARRAY)
mips = 1;
// create texture of identical format/size to store initial contents
CreateTextureImage(tex, details.internalFormat, details.curType, details.dimension,
details.width, details.height, details.depth, details.samples, mips);
// we need to set maxlevel appropriately for number of mips to force the texture to be
// complete.
// This can happen if e.g. a texture is initialised just by default with glTexImage for level
// 0 and used as a framebuffer attachment, then the implementation is fine with it.
// Unfortunately glCopyImageSubData requires completeness across all mips, a stricter
// requirement :(.
// We set max_level to mips - 1 (so mips=1 means MAX_LEVEL=0). Then restore it to the 'real'
// value we fetched above
int maxlevel = mips - 1;
gl.glTextureParameterivEXT(res.name, details.curType, eGL_TEXTURE_MAX_LEVEL,
(GLint *)&maxlevel);
bool iscomp = IsCompressedFormat(details.internalFormat);
bool avoidCopySubImage = false;
if(iscomp && VendorCheck[VendorCheck_AMD_copy_compressed_tinymips])
avoidCopySubImage = true;
if(iscomp && details.curType == eGL_TEXTURE_CUBE_MAP &&
VendorCheck[VendorCheck_AMD_copy_compressed_cubemaps])
avoidCopySubImage = true;
if(iscomp && IsGLES)
avoidCopySubImage = true;
PixelPackState pack;
PixelUnpackState unpack;
GLuint pixelPackBuffer = 0;
GLuint pixelUnpackBuffer = 0;
if(avoidCopySubImage)
{
pack.Fetch(&gl, false);
unpack.Fetch(&gl, false);
ResetPixelPackState(gl, false, 1);
ResetPixelUnpackState(gl, false, 1);
gl.glGetIntegerv(eGL_PIXEL_PACK_BUFFER_BINDING, (GLint *)&pixelPackBuffer);
gl.glGetIntegerv(eGL_PIXEL_UNPACK_BUFFER_BINDING, (GLint *)&pixelUnpackBuffer);
gl.glBindBuffer(eGL_PIXEL_PACK_BUFFER, 0);
gl.glBindBuffer(eGL_PIXEL_UNPACK_BUFFER, 0);
}
// copy over mips
for(int i = 0; i < mips; i++)
{
int w = RDCMAX(details.width >> i, 1);
int h = RDCMAX(details.height >> i, 1);
int d = RDCMAX(details.depth >> i, 1);
if(details.curType == eGL_TEXTURE_CUBE_MAP)
d *= 6;
else if(details.curType == eGL_TEXTURE_CUBE_MAP_ARRAY ||
details.curType == eGL_TEXTURE_1D_ARRAY || details.curType == eGL_TEXTURE_2D_ARRAY)
d = details.depth;
// AMD throws an error copying mips that are smaller than the block size in one dimension,
// so do copy via CPU instead (will be slow, potentially we could optimise this if there's a
// different GPU-side image copy routine that works on these dimensions. Hopefully there'll
// only be a couple of such mips).
// AMD also has issues copying cubemaps
// glCopyImageSubData does not seem to work at all for compressed textures on GLES (at least
// with some tested drivers and texture types)
if((iscomp && VendorCheck[VendorCheck_AMD_copy_compressed_tinymips] && (w < 4 || h < 4)) ||
(iscomp && VendorCheck[VendorCheck_AMD_copy_compressed_cubemaps] &&
details.curType == eGL_TEXTURE_CUBE_MAP) ||
(iscomp && IsGLES))
{
GLenum targets[] = {
eGL_TEXTURE_CUBE_MAP_POSITIVE_X, eGL_TEXTURE_CUBE_MAP_NEGATIVE_X,
eGL_TEXTURE_CUBE_MAP_POSITIVE_Y, eGL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
eGL_TEXTURE_CUBE_MAP_POSITIVE_Z, eGL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
};
int count = ARRAY_COUNT(targets);
if(details.curType != eGL_TEXTURE_CUBE_MAP)
{
targets[0] = details.curType;
count = 1;
}
for(int trg = 0; trg < count; trg++)
{
size_t size = GetCompressedByteSize(w, h, d, details.internalFormat);
byte *buf = new byte[size];
if(IsGLES)
{
details.GetCompressedImageDataGLES(i, targets[trg], size, buf);
}
else
{
// read to CPU
gl.glGetCompressedTextureImageEXT(res.name, targets[trg], i, buf);
}
// write to GPU
if(details.dimension == 1)
gl.glCompressedTextureSubImage1DEXT(tex, targets[trg], i, 0, w,
details.internalFormat, (GLsizei)size, buf);
else if(details.dimension == 2)
gl.glCompressedTextureSubImage2DEXT(tex, targets[trg], i, 0, 0, w, h,
details.internalFormat, (GLsizei)size, buf);
else if(details.dimension == 3)
gl.glCompressedTextureSubImage3DEXT(tex, targets[trg], i, 0, 0, 0, w, h, d,
details.internalFormat, (GLsizei)size, buf);
delete[] buf;
}
}
else
{
// it seems like everything explodes if I do glCopyImageSubData on a D32F_S8 texture -
// in-program the overlay gets corrupted as one UBO seems to not provide data anymore
// until it's "refreshed". It seems like a driver bug, nvidia specific. In most cases a
// program isn't going to rely on the contents of a depth-stencil buffer (shadow maps that
// it might require would be depth-only formatted).
if(details.internalFormat == eGL_DEPTH32F_STENCIL8 &&
VendorCheck[VendorCheck_NV_avoid_D32S8_copy])
RDCDEBUG("Not fetching initial contents of D32F_S8 texture");
else
gl.glCopyImageSubData(res.name, details.curType, i, 0, 0, 0, tex, details.curType, i, 0,
0, 0, w, h, d);
}
}
if(avoidCopySubImage)
{
pack.Apply(&gl, false);
unpack.Apply(&gl, false);
gl.glBindBuffer(eGL_PIXEL_PACK_BUFFER, pixelPackBuffer);
gl.glBindBuffer(eGL_PIXEL_UNPACK_BUFFER, pixelUnpackBuffer);
}
gl.glTextureParameterivEXT(res.name, details.curType, eGL_TEXTURE_MAX_LEVEL,
(GLint *)&state->maxLevel);
}
SetInitialContents(
origid, InitialContentData(res.Namespace, TextureRes(res.Context, tex), 0, (byte *)state));
}
else
{
// record texbuffer only state
GLuint bufName = 0;
gl.glGetTextureLevelParameterivEXT(res.name, details.curType, 0,
eGL_TEXTURE_BUFFER_DATA_STORE_BINDING, (GLint *)&bufName);
state->texBuffer = BufferRes(res.Context, bufName);
gl.glGetTextureLevelParameterivEXT(res.name, details.curType, 0, eGL_TEXTURE_BUFFER_OFFSET,
(GLint *)&state->texBufOffs);
gl.glGetTextureLevelParameterivEXT(res.name, details.curType, 0, eGL_TEXTURE_BUFFER_SIZE,
(GLint *)&state->texBufSize);
SetInitialContents(
origid, InitialContentData(res.Namespace, GLResource(MakeNullResource), 0, (byte *)state));
}
}
bool GLResourceManager::Force_InitialState(GLResource res, bool prepare)
{
if(res.Namespace != eResBuffer && res.Namespace != eResTexture)
return false;
// don't need to force anything if we're already including all resources
if(RenderDoc::Inst().GetCaptureOptions().RefAllResources)
return false;
GLResourceRecord *record = GetResourceRecord(res);
// if we have some viewers, check to see if they were referenced but we weren't, and force our own
// initial state inclusion.
if(record && !record->viewTextures.empty())
{
// need to prepare all such resources, just in case for the worst case.
if(prepare)
return true;
// if this data resource was referenced already, just skip
if(m_FrameReferencedResources.find(record->GetResourceID()) != m_FrameReferencedResources.end())
return false;
// see if any of our viewers were referenced
for(auto it = record->viewTextures.begin(); it != record->viewTextures.end(); ++it)
{
// if so, return true to force our inclusion, for the benefit of the view
if(m_FrameReferencedResources.find(*it) != m_FrameReferencedResources.end())
{
RDCDEBUG("Forcing inclusion of %llu for %llu", record->GetResourceID(), *it);
return true;
}
}
}
return false;
}
uint32_t GLResourceManager::GetSize_InitialState(ResourceId resid, GLResource res)
{
if(res.Namespace == eResBuffer)
{
// buffers just have their contents, no metadata needed
return GetInitialContents(resid).num + (uint32_t)WriteSerialiser::GetChunkAlignment() + 16;
}
else if(res.Namespace == eResProgram)
{
// need to estimate based on how many bindings and uniforms there are. This is a rare path -
// only happening when a program is created at runtime in the middle of a frameand we didn't
// prepare its initial contents. So we take a less efficient route by just serialising the
// current contents and using that as our size estimate, then throwing away the contents.
WriteSerialiser ser(new StreamWriter(4 * 1024), Ownership::Stream);
SCOPED_SERIALISE_CHUNK(SystemChunk::InitialContents);
SERIALISE_ELEMENT(resid);
SERIALISE_ELEMENT(res.Namespace);
SerialiseProgramBindings(ser, CaptureState::ActiveCapturing, m_GL->GetHookset(), res.name);
SerialiseProgramUniforms(ser, CaptureState::ActiveCapturing, m_GL->GetHookset(), res.name, NULL);
return (uint32_t)ser.GetWriter()->GetOffset() + 256;
}
else if(res.Namespace == eResTexture)
{
uint32_t ret = 0;
ret += sizeof(TextureStateInitialData) + 64;
TextureStateInitialData *TextureState = (TextureStateInitialData *)GetInitialContents(resid).blob;
// in these cases, no more data is serialised
if(TextureState->internalformat == eGL_NONE || TextureState->type == eGL_TEXTURE_BUFFER ||
TextureState->isView)
return ret;
bool isCompressed = IsCompressedFormat(TextureState->internalformat);
GLenum fmt = eGL_NONE;
GLenum type = eGL_NONE;
if(!isCompressed)
{
fmt = GetBaseFormat(TextureState->internalformat);
type = GetDataType(TextureState->internalformat);
}
// otherwise loop over all the mips and estimate their size
for(int i = 0; i < TextureState->mips; i++)
{
uint32_t w = RDCMAX(TextureState->width >> i, 1U);
uint32_t h = RDCMAX(TextureState->height >> i, 1U);
uint32_t d = RDCMAX(TextureState->depth >> i, 1U);
if(TextureState->type == eGL_TEXTURE_CUBE_MAP_ARRAY ||
TextureState->type == eGL_TEXTURE_1D_ARRAY || TextureState->type == eGL_TEXTURE_2D_ARRAY)
d = TextureState->depth;
uint32_t size = 0;
// calculate the actual byte size of this mip
if(isCompressed)
size = (uint32_t)GetCompressedByteSize(w, h, d, TextureState->internalformat);
else
size = (uint32_t)GetByteSize(w, h, d, fmt, type);
int targetcount = 1;
if(TextureState->type == eGL_TEXTURE_CUBE_MAP)
targetcount = 6;
for(int t = 0; t < targetcount; t++)
ret += (uint32_t)WriteSerialiser::GetChunkAlignment() + size;
}
return ret;
}
else if(res.Namespace == eResFramebuffer)
{
return sizeof(FramebufferInitialData);
}
else if(res.Namespace == eResFeedback)
{
return sizeof(FeedbackInitialData);
}
else if(res.Namespace == eResProgramPipe)
{
return sizeof(PipelineInitialData);
}
else if(res.Namespace == eResVertexArray)
{
return sizeof(VAOInitialData);
}
else if(res.Namespace == eResRenderbuffer)
{
}
else
{
RDCERR("Unexpected type of resource requiring initial state");
}
return 16;
}
template <typename SerialiserType>
bool GLResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceId resid, GLResource res)
{
m_State = m_GL->GetState();
SERIALISE_ELEMENT_LOCAL(Id, GetID(res));
SERIALISE_ELEMENT_LOCAL(Type, res.Namespace);
if(IsReplayingAndReading())
{
if(HasLiveResource(Id))
res = GetLiveResource(Id);
else
res = GLResource(MakeNullResource);
m_GL->AddResourceCurChunk(Id);
}
const GLHookSet &gl = m_GL->GetHookset();
if(Type == eResBuffer)
{
uint32_t BufferContentsSize = 0;
byte *BufferContents = NULL;
if(ser.IsWriting())
{
InitialContentData contents = GetInitialContents(Id);
res = contents.resource;
BufferContentsSize = contents.num;
BufferContents = (byte *)gl.glMapNamedBufferEXT(res.name, eGL_READ_ONLY);
if(!BufferContents)
RDCERR("Couldn't map initial contents buffer for readback!");
}
// Serialise this separately so that it can be used on reading to prepare the upload memory
SERIALISE_ELEMENT(BufferContentsSize);
if(IsReplayingAndReading())
{
if(!ser.IsErrored())
{
gl.glGenBuffers(1, &res.name);
gl.glBindBuffer(eGL_COPY_WRITE_BUFFER, res.name);
gl.glNamedBufferDataEXT(res.name, (GLsizeiptr)BufferContentsSize, NULL, eGL_STATIC_DRAW);
BufferContents = (byte *)gl.glMapNamedBufferEXT(res.name, eGL_WRITE_ONLY);
SetInitialContents(Id, InitialContentData(res.Namespace, BufferRes(m_GL->GetCtx(), res.name),
BufferContentsSize, NULL));
}
else
{
res = GLResource(MakeNullResource);
}
}
// not using SERIALISE_ELEMENT_ARRAY so we can deliberately avoid allocation - we serialise
// directly into upload memory
ser.Serialise("BufferContents", BufferContents, BufferContentsSize, SerialiserFlags::NoFlags);
if(res.name)
gl.glUnmapNamedBufferEXT(res.name);
SERIALISE_CHECK_READ_ERRORS();
}
else if(Type == eResProgram)
{
GLuint bindingsProgram = 0, uniformsProgram = 0;
std::map<GLint, GLint> *translationTable = NULL;
if(IsReplayingAndReading())
{
WrappedOpenGL::ProgramData &details = m_GL->m_Programs[GetLiveID(Id)];
GLuint initProg = gl.glCreateProgram();
std::vector<std::string> vertexOutputs;
for(size_t i = 0; i < details.shaders.size(); i++)
{
const auto &shadDetails = m_GL->m_Shaders[details.shaders[i]];
GLuint shad = gl.glCreateShader(shadDetails.type);
if(shadDetails.type == eGL_VERTEX_SHADER)
{
for(const SigParameter &sig : shadDetails.reflection.OutputSig)
{
std::string name = sig.varName;
// look for :row added to split up matrix variables
size_t colon = name.find(":row");
// remove it, if present
if(colon != std::string::npos)
name.resize(colon);
// only push matrix variables once
if(std::find(vertexOutputs.begin(), vertexOutputs.end(), name) == vertexOutputs.end())
vertexOutputs.push_back(name);
}
}
char **srcs = new char *[shadDetails.sources.size()];
for(size_t s = 0; s < shadDetails.sources.size(); s++)
srcs[s] = (char *)shadDetails.sources[s].c_str();
gl.glShaderSource(shad, (GLsizei)shadDetails.sources.size(), srcs, NULL);
SAFE_DELETE_ARRAY(srcs);
gl.glCompileShader(shad);
gl.glAttachShader(initProg, shad);
gl.glDeleteShader(shad);
}
// Some drivers optimize out uniforms if they dont change any active vertex shader outputs.
// This resulted in initProg locationTranslate table being -1 for a particular shader where
// some uniforms were only intended to affect TF. Therefore set a TF mode for all varyings.
// As the initial state program is never used for TF, this wont adversely affect anything.
std::vector<const char *> vertexOutputsPtr;
vertexOutputsPtr.resize(vertexOutputs.size());
for(size_t i = 0; i < vertexOutputs.size(); i++)
vertexOutputsPtr[i] = vertexOutputs[i].c_str();
gl.glTransformFeedbackVaryings(initProg, (GLsizei)vertexOutputsPtr.size(),
&vertexOutputsPtr[0], eGL_INTERLEAVED_ATTRIBS);
gl.glLinkProgram(initProg);
GLint status = 0;
gl.glGetProgramiv(initProg, eGL_LINK_STATUS, &status);
// if it failed to link, first remove the varyings hack above as maybe the driver is barfing
// on trying to make some output a varying
if(status == 0)
{
gl.glTransformFeedbackVaryings(initProg, 0, NULL, eGL_INTERLEAVED_ATTRIBS);
gl.glLinkProgram(initProg);
gl.glGetProgramiv(initProg, eGL_LINK_STATUS, &status);
}
// if it failed to link, try again as a separable program.
// we can't do this by default because of the silly rules meaning
// shaders need fixup to be separable-compatible.
if(status == 0)
{
gl.glProgramParameteri(initProg, eGL_PROGRAM_SEPARABLE, 1);
gl.glLinkProgram(initProg);
gl.glGetProgramiv(initProg, eGL_LINK_STATUS, &status);
}
if(status == 0)
{
if(details.shaders.size() == 0)
{
RDCWARN("No shaders attached to program");
}
else
{
char buffer[1025] = {0};
gl.glGetProgramInfoLog(initProg, 1024, NULL, buffer);
RDCERR("Link error: %s", buffer);
}
}
// normally we'd serialise programs and uniforms into the initial state program, but on some
// drivers uniform locations can change between it and the live program, so we serialise the
// uniforms directly into the live program, then copy back to the initial state so that we
// have a pristine copy of them for later use.
bindingsProgram = initProg;
uniformsProgram = GetLiveResource(Id).name;
translationTable = &details.locationTranslate;
}
if(ser.IsWriting())
{
// most of the time Prepare_InitialState sets the serialise chunk directly on write, but if a
// program is newly created within a frame we won't have prepared its initial contents, so we
// need to be ready to write it out here.
bindingsProgram = uniformsProgram = res.name;
}
SerialiseProgramBindings(ser, m_State, gl, bindingsProgram);
// re-link the program to set the new attrib bindings
if(IsReplayingAndReading() && !ser.IsErrored())
gl.glLinkProgram(bindingsProgram);
SerialiseProgramUniforms(ser, m_State, gl, uniformsProgram, translationTable);
SERIALISE_CHECK_READ_ERRORS();
if(IsReplayingAndReading())
{
// see above for why we're copying this back
CopyProgramUniforms(gl, uniformsProgram, bindingsProgram);
SetInitialContents(
Id,
InitialContentData(res.Namespace, ProgramRes(m_GL->GetCtx(), bindingsProgram), 0, NULL));
}
}
else if(Type == eResTexture)
{
GLuint ppb = 0, pub = 0;
PixelPackState pack;
PixelUnpackState unpack;
// save and restore pixel pack/unpack state. We only need one or the other but for clarity we
// push and pop both always.
if(ser.IsWriting() || !IsStructuredExporting(m_State))
{
gl.glGetIntegerv(eGL_PIXEL_PACK_BUFFER_BINDING, (GLint *)&ppb);
gl.glGetIntegerv(eGL_PIXEL_UNPACK_BUFFER_BINDING, (GLint *)&pub);
gl.glBindBuffer(eGL_PIXEL_PACK_BUFFER, 0);
gl.glBindBuffer(eGL_PIXEL_UNPACK_BUFFER, 0);
pack.Fetch(&gl, false);
unpack.Fetch(&gl, false);
ResetPixelPackState(gl, false, 1);
ResetPixelUnpackState(gl, false, 1);
}
// serialise the texture metadata which was fetched during state preparation
TextureStateInitialData TextureState;
if(ser.IsWriting())
{
TextureStateInitialData *initialdata = (TextureStateInitialData *)GetInitialContents(Id).blob;
memcpy(&TextureState, initialdata, sizeof(TextureState));
}
SERIALISE_ELEMENT(TextureState);
// only continue with serialising the contents if the format is valid (storage allocated).
// Otherwise this texture has no initial state to apply
if(TextureState.internalformat != eGL_NONE && !ser.IsErrored())
{
WrappedOpenGL::TextureData &details = m_GL->m_Textures[GetID(res)];
TextureStateInitialData *state = NULL;
// on replay we need the state as an initial contents blob, so copy it off
if(IsReplayingAndReading() && !ser.IsErrored())
{
state = (TextureStateInitialData *)AllocAlignedBuffer(sizeof(TextureStateInitialData));
memcpy(state, &TextureState, sizeof(TextureStateInitialData));
}
if(TextureState.type == eGL_TEXTURE_BUFFER || TextureState.isView)
{
// no contents to copy for texture buffer (it's copied under the buffer)
// same applies for texture views, their data is copies under the aliased texture.
// We just set the metadata blob.
if(IsReplayingAndReading() && !ser.IsErrored())
{
SetInitialContents(
Id, InitialContentData(res.Namespace, GLResource(MakeNullResource), 0, (byte *)state));
}
}
else
{
// we need to treat compressed textures differently, so check it
bool isCompressed = IsCompressedFormat(TextureState.internalformat);
// this array will be used to iterate over cubemap faces. If we're *not* uploading a
// cubemap, we change the targetcount to 1 below and overwrite the first element in the
// array with the proper target.
GLenum targets[] = {
eGL_TEXTURE_CUBE_MAP_POSITIVE_X, eGL_TEXTURE_CUBE_MAP_NEGATIVE_X,
eGL_TEXTURE_CUBE_MAP_POSITIVE_Y, eGL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
eGL_TEXTURE_CUBE_MAP_POSITIVE_Z, eGL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
};
int targetcount = ARRAY_COUNT(targets);
if(TextureState.type != eGL_TEXTURE_CUBE_MAP)
{
targets[0] = TextureState.type;
targetcount = 1;
}
// For real textures, if number of mips isn't sufficient, make sure to initialise the lower
// levels. This could happen if e.g. a texture is init'd with glTexImage(level = 0), then
// after we stop tracking it glGenerateMipmap is called.
if(IsReplayingAndReading() && !ser.IsErrored())
{
// this is only relevant for non-immutable textures
GLint immut = 0;
gl.glGetTextureParameterivEXT(res.name, TextureState.type, eGL_TEXTURE_IMMUTABLE_FORMAT,
&immut);
GLenum dummy = eGL_RGBA;
EmulateLuminanceFormat(gl, res.name, TextureState.type, TextureState.internalformat, dummy);
if(immut == 0)
{
GLsizei w = (GLsizei)TextureState.width;
GLsizei h = (GLsizei)TextureState.height;
GLsizei d = (GLsizei)TextureState.depth;
// see how many mips we actually have available
int liveMips = GetNumMips(gl, TextureState.type, res.name, w, h, d);
std::vector<byte> scratchBuf;
// loop over the number of mips we should have
for(int m = 1; m < TextureState.mips; m++)
{
w = RDCMAX(1, w >> 1);
h = RDCMAX(1, h >> 1);
d = RDCMAX(1, d >> 1);
if(TextureState.type == eGL_TEXTURE_CUBE_MAP_ARRAY ||
TextureState.type == eGL_TEXTURE_1D_ARRAY ||
TextureState.type == eGL_TEXTURE_2D_ARRAY)
d = (GLsizei)TextureState.depth;
// if this mip doesn't exist yet, we must create it with dummy data.
if(m >= liveMips)
{
for(int t = 0; t < targetcount; t++)
{
if(isCompressed)
{
GLsizei compSize =
(GLsizei)GetCompressedByteSize(w, h, d, TextureState.internalformat);
scratchBuf.resize(compSize);
if(TextureState.dim == 1)
gl.glCompressedTextureImage1DEXT(res.name, targets[t], m,
TextureState.internalformat, w, 0, compSize,
&scratchBuf[0]);
else if(TextureState.dim == 2)
gl.glCompressedTextureImage2DEXT(res.name, targets[t], m,
TextureState.internalformat, w, h, 0,
compSize, &scratchBuf[0]);
else if(TextureState.dim == 3)
gl.glCompressedTextureImage3DEXT(res.name, targets[t], m,
TextureState.internalformat, w, h, d, 0,
compSize, &scratchBuf[0]);
}
else
{
if(TextureState.dim == 1)
gl.glTextureImage1DEXT(res.name, targets[t], m, TextureState.internalformat,
(GLsizei)w, 0,
GetBaseFormat(TextureState.internalformat),
GetDataType(TextureState.internalformat), NULL);
else if(TextureState.dim == 2)
gl.glTextureImage2DEXT(res.name, targets[t], m, TextureState.internalformat,
(GLsizei)w, (GLsizei)h, 0,
GetBaseFormat(TextureState.internalformat),
GetDataType(TextureState.internalformat), NULL);
else if(TextureState.dim == 3)
gl.glTextureImage3DEXT(res.name, targets[t], m, TextureState.internalformat,
(GLsizei)w, (GLsizei)h, (GLsizei)d, 0,
GetBaseFormat(TextureState.internalformat),
GetDataType(TextureState.internalformat), NULL);
}
}
}
}
}
}
// finished ensuring the texture has the right number of mip levels.
GLuint tex = 0;
GLuint prevtex = 0;
// push the texture binding
if(!IsStructuredExporting(m_State) && !ser.IsErrored())
gl.glGetIntegerv(TextureBinding(TextureState.type), (GLint *)&prevtex);
// create texture of identical format/size as the live resource to store initial contents
if(IsReplayingAndReading() && !ser.IsErrored())
{
gl.glGenTextures(1, &tex);
gl.glBindTexture(TextureState.type, tex);
CreateTextureImage(tex, TextureState.internalformat, TextureState.type, TextureState.dim,
TextureState.width, TextureState.height, TextureState.depth,
TextureState.samples, TextureState.mips);
}
else if(ser.IsWriting())
{
// on writing, bind the prepared texture with initial contents to grab
tex = GetInitialContents(Id).resource.name;
gl.glBindTexture(TextureState.type, tex);
}
// multisample textures have no mips
if(TextureState.type == eGL_TEXTURE_2D_MULTISAMPLE ||
TextureState.type == eGL_TEXTURE_2D_MULTISAMPLE_ARRAY)
TextureState.mips = 1;
if(TextureState.samples > 1)
{
GLNOTIMP("Not implemented - initial states of multisampled textures");
}
else
{
GLenum fmt = eGL_NONE;
GLenum type = eGL_NONE;
uint32_t size = 0;
// fetch the maximum possible size that any mip/slice could take, so we can allocate
// scratch memory.
if(isCompressed)
{
size = (uint32_t)GetCompressedByteSize(TextureState.width, TextureState.height,
TextureState.depth, TextureState.internalformat);
}
else
{
fmt = GetBaseFormat(TextureState.internalformat);
type = GetDataType(TextureState.internalformat);
size = (uint32_t)GetByteSize(TextureState.width, TextureState.height,
TextureState.depth, fmt, type);
}
// on read and write, we allocate a single buffer big enough for all mips and re-use it
// to avoid repeated new/free.
byte *scratchBuf = AllocAlignedBuffer(size);
// loop over all the available mips
for(int i = 0; i < TextureState.mips; i++)
{
uint32_t w = RDCMAX(TextureState.width >> i, 1U);
uint32_t h = RDCMAX(TextureState.height >> i, 1U);
uint32_t d = RDCMAX(TextureState.depth >> i, 1U);
if(TextureState.type == eGL_TEXTURE_CUBE_MAP_ARRAY ||
TextureState.type == eGL_TEXTURE_1D_ARRAY || TextureState.type == eGL_TEXTURE_2D_ARRAY)
d = TextureState.depth;
// calculate the actual byte size of this mip
if(isCompressed)
size = (uint32_t)GetCompressedByteSize(w, h, d, TextureState.internalformat);
else
size = (uint32_t)GetByteSize(w, h, d, fmt, type);
// loop over the number of targets (this will only ever be >1 for cubemaps)
for(int trg = 0; trg < targetcount; trg++)
{
// when writing, fetch the source data out of the texture
if(ser.IsWriting())
{
if(isCompressed)
{
if(IsGLES)
details.GetCompressedImageDataGLES(i, targets[trg], size, scratchBuf);
else
gl.glGetCompressedTextureImageEXT(tex, targets[trg], i, scratchBuf);
}
else
{
// we avoid glGetTextureImageEXT as it seems buggy for cubemap faces
gl.glGetTexImage(targets[trg], i, fmt, type, scratchBuf);
}
}
// serialise without allocating memory as we already have our scratch buf sized.
ser.Serialise("SubresourceContents", scratchBuf, size, SerialiserFlags::NoFlags);
// on replay, restore the data into the initial contents texture
if(IsReplayingAndReading() && !ser.IsErrored())
{
if(isCompressed)
{
if(IsGLES)
{
details.compressedData[i].resize(size);
memcpy(details.compressedData[i].data(), scratchBuf, size);
}
if(TextureState.dim == 1)
gl.glCompressedTextureSubImage1DEXT(tex, targets[trg], i, 0, w,
TextureState.internalformat, (GLsizei)size,
scratchBuf);
else if(TextureState.dim == 2)
gl.glCompressedTextureSubImage2DEXT(tex, targets[trg], i, 0, 0, w, h,
TextureState.internalformat, (GLsizei)size,
scratchBuf);
else if(TextureState.dim == 3)
gl.glCompressedTextureSubImage3DEXT(tex, targets[trg], i, 0, 0, 0, w, h, d,
TextureState.internalformat, (GLsizei)size,
scratchBuf);
}
else
{
if(TextureState.dim == 1)
gl.glTextureSubImage1DEXT(tex, targets[trg], i, 0, w, fmt, type, scratchBuf);
else if(TextureState.dim == 2)
gl.glTextureSubImage2DEXT(tex, targets[trg], i, 0, 0, w, h, fmt, type,
scratchBuf);
else if(TextureState.dim == 3)
gl.glTextureSubImage3DEXT(tex, targets[trg], i, 0, 0, 0, w, h, d, fmt, type,
scratchBuf);
}
}
}
}
// free our scratch buffer
FreeAlignedBuffer(scratchBuf);
}
// restore the previous texture binding
if(!IsStructuredExporting(m_State) && !ser.IsErrored())
gl.glBindTexture(TextureState.type, prevtex);
if(IsReplayingAndReading() && !ser.IsErrored())
{
SetInitialContents(Id, InitialContentData(res.Namespace, TextureRes(m_GL->GetCtx(), tex),
0, (byte *)state));
}
}
}
// restore pixel (un)packing state
if(ser.IsWriting() || !IsStructuredExporting(m_State))
{
gl.glBindBuffer(eGL_PIXEL_PACK_BUFFER, ppb);
gl.glBindBuffer(eGL_PIXEL_UNPACK_BUFFER, pub);
pack.Apply(&gl, false);
unpack.Apply(&gl, false);
}
SERIALISE_CHECK_READ_ERRORS();
}
else if(Type == eResFramebuffer)
{
FramebufferInitialData FramebufferState;
if(ser.IsWriting())
{
FramebufferInitialData *initialdata = (FramebufferInitialData *)GetInitialContents(Id).blob;
memcpy(&FramebufferState, initialdata, sizeof(FramebufferState));
}
SERIALISE_ELEMENT(FramebufferState);
SERIALISE_CHECK_READ_ERRORS();
if(IsReplayingAndReading())
{
byte *blob = AllocAlignedBuffer(sizeof(FramebufferState));
memcpy(blob, &FramebufferState, sizeof(FramebufferState));
SetInitialContents(Id,
InitialContentData(res.Namespace, GLResource(MakeNullResource), 0, blob));
}
}
else if(Type == eResFeedback)
{
FeedbackInitialData TransformFeedbackState;
if(ser.IsWriting())
{
FeedbackInitialData *initialdata = (FeedbackInitialData *)GetInitialContents(Id).blob;
memcpy(&TransformFeedbackState, initialdata, sizeof(TransformFeedbackState));
}
SERIALISE_ELEMENT(TransformFeedbackState);
SERIALISE_CHECK_READ_ERRORS();
if(IsReplayingAndReading())
{
byte *blob = AllocAlignedBuffer(sizeof(TransformFeedbackState));
memcpy(blob, &TransformFeedbackState, sizeof(TransformFeedbackState));
SetInitialContents(Id,
InitialContentData(res.Namespace, GLResource(MakeNullResource), 0, blob));
}
}
else if(Type == eResProgramPipe)
{
PipelineInitialData ProgramPipelineState;
if(ser.IsWriting())
{
PipelineInitialData *initialdata = (PipelineInitialData *)GetInitialContents(Id).blob;
memcpy(&ProgramPipelineState, initialdata, sizeof(ProgramPipelineState));
}
SERIALISE_ELEMENT(ProgramPipelineState);
SERIALISE_CHECK_READ_ERRORS();
if(IsReplayingAndReading())
{
byte *blob = AllocAlignedBuffer(sizeof(ProgramPipelineState));
memcpy(blob, &ProgramPipelineState, sizeof(ProgramPipelineState));
SetInitialContents(Id,
InitialContentData(res.Namespace, GLResource(MakeNullResource), 0, blob));
}
}
else if(Type == eResVertexArray)
{
VAOInitialData VAOState;
if(ser.IsWriting())
{
VAOInitialData *initialdata = (VAOInitialData *)GetInitialContents(Id).blob;
memcpy(&VAOState, initialdata, sizeof(VAOState));
}
SERIALISE_ELEMENT(VAOState);
SERIALISE_CHECK_READ_ERRORS();
if(IsReplayingAndReading())
{
byte *blob = AllocAlignedBuffer(sizeof(VAOState));
memcpy(blob, &VAOState, sizeof(VAOState));
SetInitialContents(Id,
InitialContentData(res.Namespace, GLResource(MakeNullResource), 0, blob));
}
}
else if(Type == eResRenderbuffer)
{
RDCWARN(
"Technically you could try and readback the contents of a RenderBuffer via pixel copy.");
RDCWARN("Currently we don't support that though, and initial contents will be uninitialised.");
}
else
{
RDCERR("Unexpected type of resource requiring initial state");
}
return true;
}
template bool GLResourceManager::Serialise_InitialState<>(ReadSerialiser &ser, ResourceId resid,
GLResource res);
template bool GLResourceManager::Serialise_InitialState<>(WriteSerialiser &ser, ResourceId resid,
GLResource res);
void GLResourceManager::Create_InitialState(ResourceId id, GLResource live, bool hasData)
{
if(IsStructuredExporting(m_State))
return;
if(live.Namespace == eResTexture)
{
// we basically need to do exactly the same as Prepare_InitialState -
// save current texture state, create a duplicate object, and save
// the current contents into that duplicate object
// in future if we skip RT contents for write-before-read RTs, we could mark
// textures to be cleared instead of copied.
PrepareTextureInitialContents(GetID(live), id, live);
}
else if(live.Namespace == eResVertexArray)
{
byte *data = AllocAlignedBuffer(sizeof(VAOInitialData));
RDCEraseMem(data, sizeof(VAOInitialData));
SetInitialContents(id,
InitialContentData(eResVertexArray, GLResource(MakeNullResource), 0, data));
Prepare_InitialState(live, data);
}
else if(live.Namespace != eResBuffer && live.Namespace != eResProgram &&
live.Namespace != eResRenderbuffer)
{
RDCUNIMPLEMENTED("Expect all initial states to be created & not skipped, presently");
}
}
void GLResourceManager::Apply_InitialState(GLResource live, InitialContentData initial)
{
const GLHookSet &gl = m_GL->GetHookset();
if(live.Namespace == eResBuffer)
{
// save old bindings
GLuint oldbuf1 = 0, oldbuf2 = 0;
gl.glGetIntegerv(eGL_COPY_READ_BUFFER_BINDING, (GLint *)&oldbuf1);
gl.glGetIntegerv(eGL_COPY_WRITE_BUFFER_BINDING, (GLint *)&oldbuf2);
// bind the immutable contents for copying
gl.glBindBuffer(eGL_COPY_READ_BUFFER, initial.resource.name);
// bind the live buffer for copying
gl.glBindBuffer(eGL_COPY_WRITE_BUFFER, live.name);
// do the actual copy
gl.glCopyBufferSubData(eGL_COPY_READ_BUFFER, eGL_COPY_WRITE_BUFFER, 0, 0,
(GLsizeiptr)initial.num);
// restore old bindings
gl.glBindBuffer(eGL_COPY_READ_BUFFER, oldbuf1);
gl.glBindBuffer(eGL_COPY_WRITE_BUFFER, oldbuf2);
}
else if(live.Namespace == eResTexture)
{
ResourceId Id = GetID(live);
WrappedOpenGL::TextureData &details = m_GL->m_Textures[Id];
TextureStateInitialData *state = (TextureStateInitialData *)initial.blob;
if(details.curType != eGL_TEXTURE_BUFFER)
{
GLuint tex = initial.resource.name;
if(initial.resource != GLResource(MakeNullResource) && tex != 0)
{
int mips = GetNumMips(gl, details.curType, tex, details.width, details.height, details.depth);
// we need to set maxlevel appropriately for number of mips to force the texture to be
// complete. This can happen if e.g. a texture is initialised just by default with
// glTexImage for level 0 and used as a framebuffer attachment, then the implementation is
// fine with it.
// Unfortunately glCopyImageSubData requires completeness across all mips, a stricter
// requirement :(.
// We set max_level to mips - 1 (so mips=1 means MAX_LEVEL=0). Then below where we set the
// texture state, the correct MAX_LEVEL is set to whatever the program had.
int maxlevel = mips - 1;
gl.glTextureParameterivEXT(live.name, details.curType, eGL_TEXTURE_MAX_LEVEL,
(GLint *)&maxlevel);
bool iscomp = IsCompressedFormat(details.internalFormat);
bool avoidCopySubImage = false;
if(iscomp && VendorCheck[VendorCheck_AMD_copy_compressed_tinymips])
avoidCopySubImage = true;
if(iscomp && details.curType == eGL_TEXTURE_CUBE_MAP &&
VendorCheck[VendorCheck_AMD_copy_compressed_cubemaps])
avoidCopySubImage = true;
if(iscomp && IsGLES)
avoidCopySubImage = true;
PixelPackState pack;
PixelUnpackState unpack;
if(avoidCopySubImage)
{
pack.Fetch(&gl, false);
unpack.Fetch(&gl, false);
ResetPixelPackState(gl, false, 1);
ResetPixelUnpackState(gl, false, 1);
}
// copy over mips
for(int i = 0; i < mips; i++)
{
int w = RDCMAX(details.width >> i, 1);
int h = RDCMAX(details.height >> i, 1);
int d = RDCMAX(details.depth >> i, 1);
if(details.curType == eGL_TEXTURE_CUBE_MAP)
d *= 6;
else if(details.curType == eGL_TEXTURE_CUBE_MAP_ARRAY ||
details.curType == eGL_TEXTURE_1D_ARRAY || details.curType == eGL_TEXTURE_2D_ARRAY)
d = details.depth;
// AMD throws an error copying mips that are smaller than the block size in one dimension,
// so do copy via CPU instead (will be slow, potentially we could optimise this if there's
// a different GPU-side image copy routine that works on these dimensions. Hopefully
// there'll only be a couple of such mips).
// AMD also has issues copying cubemaps
if((iscomp && VendorCheck[VendorCheck_AMD_copy_compressed_tinymips] && (w < 4 || h < 4)) ||
(iscomp && VendorCheck[VendorCheck_AMD_copy_compressed_cubemaps] &&
details.curType == eGL_TEXTURE_CUBE_MAP) ||
(iscomp && IsGLES))
{
GLenum targets[] = {
eGL_TEXTURE_CUBE_MAP_POSITIVE_X, eGL_TEXTURE_CUBE_MAP_NEGATIVE_X,
eGL_TEXTURE_CUBE_MAP_POSITIVE_Y, eGL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
eGL_TEXTURE_CUBE_MAP_POSITIVE_Z, eGL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
};
int count = ARRAY_COUNT(targets);
if(details.curType != eGL_TEXTURE_CUBE_MAP)
{
targets[0] = details.curType;
count = 1;
}
for(int trg = 0; trg < count; trg++)
{
size_t size = GetCompressedByteSize(w, h, d, details.internalFormat);
if(details.curType == eGL_TEXTURE_CUBE_MAP)
size /= 6;
byte *buf = new byte[size];
if(IsGLES)
{
details.GetCompressedImageDataGLES(i, targets[trg], size, buf);
}
else
{
// read to CPU
gl.glGetCompressedTextureImageEXT(tex, targets[trg], i, buf);
}
// write to GPU
if(details.dimension == 1)
gl.glCompressedTextureSubImage1DEXT(live.name, targets[trg], i, 0, w,
details.internalFormat, (GLsizei)size, buf);
else if(details.dimension == 2)
gl.glCompressedTextureSubImage2DEXT(live.name, targets[trg], i, 0, 0, w, h,
details.internalFormat, (GLsizei)size, buf);
else if(details.dimension == 3)
gl.glCompressedTextureSubImage3DEXT(live.name, targets[trg], i, 0, 0, 0, w, h, d,
details.internalFormat, (GLsizei)size, buf);
delete[] buf;
}
}
else
{
// it seems like everything explodes if I do glCopyImageSubData on a D32F_S8 texture -
// on replay loads of things get heavily corrupted - probably the same as the problems
// we get in-program, but magnified. It seems like a driver bug, nvidia specific.
// In most cases a program isn't going to rely on the contents of a depth-stencil buffer
// (shadow maps that it might require would be depth-only formatted).
if(details.internalFormat == eGL_DEPTH32F_STENCIL8 &&
VendorCheck[VendorCheck_NV_avoid_D32S8_copy])
RDCDEBUG("Not fetching initial contents of D32F_S8 texture");
else
gl.glCopyImageSubData(tex, details.curType, i, 0, 0, 0, live.name, details.curType, i,
0, 0, 0, w, h, d);
}
}
if(avoidCopySubImage)
{
pack.Apply(&gl, false);
unpack.Apply(&gl, false);
}
}
bool ms = (details.curType == eGL_TEXTURE_2D_MULTISAMPLE ||
details.curType == eGL_TEXTURE_2D_MULTISAMPLE_ARRAY);
if((state->depthMode == eGL_DEPTH_COMPONENT || state->depthMode == eGL_STENCIL_INDEX) &&
HasExt[ARB_stencil_texturing])
gl.glTextureParameterivEXT(live.name, details.curType, eGL_DEPTH_STENCIL_TEXTURE_MODE,
(GLint *)&state->depthMode);
if((details.curType == eGL_TEXTURE_CUBE_MAP || details.curType == eGL_TEXTURE_CUBE_MAP_ARRAY) &&
HasExt[ARB_seamless_cubemap_per_texture])
gl.glTextureParameterivEXT(live.name, details.curType, eGL_TEXTURE_CUBE_MAP_SEAMLESS,
(GLint *)&state->seamless);
gl.glTextureParameterivEXT(live.name, details.curType, eGL_TEXTURE_BASE_LEVEL,
(GLint *)&state->baseLevel);
gl.glTextureParameterivEXT(live.name, details.curType, eGL_TEXTURE_MAX_LEVEL,
(GLint *)&state->maxLevel);
// assume that emulated (luminance, alpha-only etc) textures are not swizzled
if(!details.emulated && (HasExt[ARB_texture_swizzle] || HasExt[EXT_texture_swizzle]))
{
SetTextureSwizzle(gl, live.name, details.curType, state->swizzle);
}
if(!ms)
{
gl.glTextureParameterivEXT(live.name, details.curType, eGL_TEXTURE_SRGB_DECODE_EXT,
(GLint *)&state->srgbDecode);
gl.glTextureParameterivEXT(live.name, details.curType, eGL_TEXTURE_COMPARE_FUNC,
(GLint *)&state->compareFunc);
gl.glTextureParameterivEXT(live.name, details.curType, eGL_TEXTURE_COMPARE_MODE,
(GLint *)&state->compareMode);
gl.glTextureParameterivEXT(live.name, details.curType, eGL_TEXTURE_MIN_FILTER,
(GLint *)&state->minFilter);
gl.glTextureParameterivEXT(live.name, details.curType, eGL_TEXTURE_MAG_FILTER,
(GLint *)&state->magFilter);
gl.glTextureParameterivEXT(live.name, details.curType, eGL_TEXTURE_WRAP_R,
(GLint *)&state->wrap[0]);
gl.glTextureParameterivEXT(live.name, details.curType, eGL_TEXTURE_WRAP_S,
(GLint *)&state->wrap[1]);
gl.glTextureParameterivEXT(live.name, details.curType, eGL_TEXTURE_WRAP_T,
(GLint *)&state->wrap[2]);
gl.glTextureParameterfvEXT(live.name, details.curType, eGL_TEXTURE_BORDER_COLOR,
state->border);
if(!IsGLES)
gl.glTextureParameterfvEXT(live.name, details.curType, eGL_TEXTURE_LOD_BIAS,
&state->lodBias);
if(details.curType != eGL_TEXTURE_RECTANGLE)
{
gl.glTextureParameterfvEXT(live.name, details.curType, eGL_TEXTURE_MIN_LOD, &state->minLod);
gl.glTextureParameterfvEXT(live.name, details.curType, eGL_TEXTURE_MAX_LOD, &state->maxLod);
}
}
}
else
{
GLuint buffer = state->texBuffer.name;
GLenum fmt = details.internalFormat;
// update width from here as it's authoratitive - the texture might have been resized in
// multiple rebinds that we will not have serialised before.
details.width =
state->texBufSize / uint32_t(GetByteSize(1, 1, 1, GetBaseFormat(fmt), GetDataType(fmt)));
if(gl.glTextureBufferRangeEXT)
{
// restore texbuffer only state
gl.glTextureBufferRangeEXT(live.name, eGL_TEXTURE_BUFFER, details.internalFormat, buffer,
state->texBufOffs, state->texBufSize);
}
else
{
uint32_t bufSize = 0;
gl.glGetNamedBufferParameterivEXT(buffer, eGL_BUFFER_SIZE, (GLint *)&bufSize);
if(state->texBufOffs > 0 || state->texBufSize > bufSize)
{
const char *msg =
"glTextureBufferRangeEXT is not supported on your GL implementation, but is needed "
"for correct replay.\n"
"The original capture created a texture buffer with a range - replay will use the "
"whole buffer, which is likely incorrect.";
RDCERR("%s", msg);
m_GL->AddDebugMessage(MessageCategory::Resource_Manipulation, MessageSeverity::High,
MessageSource::IncorrectAPIUse, msg);
}
gl.glTextureBufferEXT(live.name, eGL_TEXTURE_BUFFER, details.internalFormat, buffer);
}
}
}
else if(live.Namespace == eResProgram)
{
ResourceId Id = GetID(live);
const WrappedOpenGL::ProgramData &prog = m_GL->m_Programs[Id];
if(prog.stageShaders[0] != ResourceId())
CopyProgramAttribBindings(gl, initial.resource.name, live.name,
&m_GL->m_Shaders[prog.stageShaders[0]].reflection);
if(prog.stageShaders[4] != ResourceId())
CopyProgramFragDataBindings(gl, initial.resource.name, live.name,
&m_GL->m_Shaders[prog.stageShaders[4]].reflection);
// we need to re-link the program to apply the bindings, as long as it's linkable.
// See the comment on shaderProgramUnlinkable for more information.
if(!prog.shaderProgramUnlinkable)
gl.glLinkProgram(live.name);
CopyProgramUniforms(gl, initial.resource.name, live.name);
}
else if(live.Namespace == eResFramebuffer)
{
FramebufferInitialData *data = (FramebufferInitialData *)initial.blob;
if(data->valid)
{
GLuint prevread = 0, prevdraw = 0;
gl.glGetIntegerv(eGL_DRAW_FRAMEBUFFER_BINDING, (GLint *)&prevdraw);
gl.glGetIntegerv(eGL_READ_FRAMEBUFFER_BINDING, (GLint *)&prevread);
gl.glBindFramebuffer(eGL_DRAW_FRAMEBUFFER, live.name);
gl.glBindFramebuffer(eGL_READ_FRAMEBUFFER, live.name);
GLint numCols = 8;
gl.glGetIntegerv(eGL_MAX_COLOR_ATTACHMENTS, &numCols);
for(int i = 0; i < (int)ARRAY_COUNT(data->Attachments); i++)
{
FramebufferAttachmentData &a = data->Attachments[i];
GLenum attachment = FramebufferInitialData::attachmentNames[i];
if(attachment != eGL_DEPTH_ATTACHMENT && attachment != eGL_STENCIL_ATTACHMENT &&
attachment != eGL_DEPTH_STENCIL_ATTACHMENT)
{
// color attachment
int attachNum = attachment - eGL_COLOR_ATTACHMENT0;
if(attachNum >= numCols) // attachment is invalid on this device
continue;
}
GLuint obj = a.obj.name;
if(a.obj.Namespace == eResRenderbuffer && obj)
{
gl.glNamedFramebufferRenderbufferEXT(live.name, attachment, eGL_RENDERBUFFER, obj);
}
else
{
if(!a.layered && obj)
{
// we use old-style non-DSA for this because binding cubemap faces with EXT_dsa
// is completely messed up and broken
// if obj is a cubemap use face-specific targets
WrappedOpenGL::TextureData &details = m_GL->m_Textures[GetID(a.obj)];
if(details.curType == eGL_TEXTURE_CUBE_MAP)
{
GLenum faces[] = {
eGL_TEXTURE_CUBE_MAP_POSITIVE_X, eGL_TEXTURE_CUBE_MAP_NEGATIVE_X,
eGL_TEXTURE_CUBE_MAP_POSITIVE_Y, eGL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
eGL_TEXTURE_CUBE_MAP_POSITIVE_Z, eGL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
};
if(a.layer < 6)
{
gl.glFramebufferTexture2D(eGL_DRAW_FRAMEBUFFER, attachment, faces[a.layer], obj,
a.level);
}
else
{
RDCWARN("Invalid layer %u used to bind cubemap to framebuffer. Binding POSITIVE_X");
gl.glFramebufferTexture2D(eGL_DRAW_FRAMEBUFFER, attachment, faces[0], obj, a.level);
}
}
else if(details.curType == eGL_TEXTURE_CUBE_MAP_ARRAY ||
details.curType == eGL_TEXTURE_1D_ARRAY ||
details.curType == eGL_TEXTURE_2D_ARRAY)
{
gl.glFramebufferTextureLayer(eGL_DRAW_FRAMEBUFFER, attachment, obj, a.level, a.layer);
}
else
{
RDCASSERT(a.layer == 0);
gl.glNamedFramebufferTextureEXT(live.name, attachment, obj, a.level);
}
}
else
{
gl.glNamedFramebufferTextureEXT(live.name, attachment, obj, a.level);
}
}
}
// set invalid caps to GL_COLOR_ATTACHMENT0
for(int i = 0; i < (int)ARRAY_COUNT(data->DrawBuffers); i++)
if(data->DrawBuffers[i] == eGL_BACK || data->DrawBuffers[i] == eGL_FRONT)
data->DrawBuffers[i] = eGL_COLOR_ATTACHMENT0;
if(data->ReadBuffer == eGL_BACK || data->ReadBuffer == eGL_FRONT)
data->ReadBuffer = eGL_COLOR_ATTACHMENT0;
GLuint maxDraws = 0;
gl.glGetIntegerv(eGL_MAX_DRAW_BUFFERS, (GLint *)&maxDraws);
gl.glDrawBuffers(RDCMIN(maxDraws, (GLuint)ARRAY_COUNT(data->DrawBuffers)), data->DrawBuffers);
gl.glReadBuffer(data->ReadBuffer);
gl.glBindFramebuffer(eGL_DRAW_FRAMEBUFFER, prevdraw);
gl.glBindFramebuffer(eGL_READ_FRAMEBUFFER, prevread);
}
}
else if(live.Namespace == eResFeedback)
{
FeedbackInitialData *data = (FeedbackInitialData *)initial.blob;
if(data->valid)
{
GLuint prevfeedback = 0;
gl.glGetIntegerv(eGL_TRANSFORM_FEEDBACK_BINDING, (GLint *)&prevfeedback);
gl.glBindTransformFeedback(eGL_TRANSFORM_FEEDBACK, live.name);
GLint maxCount = 0;
gl.glGetIntegerv(eGL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS, &maxCount);
for(int i = 0; i < (int)ARRAY_COUNT(data->Buffer) && i < maxCount; i++)
gl.glBindBufferRange(eGL_TRANSFORM_FEEDBACK_BUFFER, i, data->Buffer[i].name,
(GLintptr)data->Offset[i], (GLsizei)data->Size[i]);
gl.glBindTransformFeedback(eGL_TRANSFORM_FEEDBACK, prevfeedback);
}
}
else if(live.Namespace == eResProgramPipe)
{
PipelineInitialData *data = (PipelineInitialData *)initial.blob;
if(data->valid)
{
// we need to bind the same program to all relevant stages at once. So since there's only 5
// stages to worry about (compute can't be shared) we just do an O(N^2) search
for(int a = 0; a < 5; a++)
{
// ignore any empty binds
if(data->programs[a].name == 0)
continue;
// this bit has a program. First search backwards to see if it was already bound previously.
bool previous = false;
for(int b = 0; b < a; b++)
if(data->programs[a].name == data->programs[b].name)
previous = true;
// if we found a match behind us, that means we already bound this program back then -
// continue
if(previous)
continue;
// now build up the bitmask that we'll bind with. Starting with the current bit, searching
// forwards
GLbitfield stages = 1 << a;
for(int b = a + 1; b < 5; b++)
if(data->programs[a].name == data->programs[b].name)
stages |= (1 << b);
// bind the program on all relevant stages
gl.glUseProgramStages(live.name, stages, data->programs[a].name);
// now we can continue - any of the stages we just bound will discard themselves with the
// 'previous' check above.
}
// if we have a compute program, bind that. It's outside of the others since it can't be
// shared
if(data->programs[5].name)
gl.glUseProgramStages(live.name, eGL_COMPUTE_SHADER_BIT, data->programs[5].name);
}
}
else if(live.Namespace == eResVertexArray)
{
VAOInitialData *initialdata = (VAOInitialData *)initial.blob;
if(initialdata->valid)
{
GLuint VAO = 0;
gl.glGetIntegerv(eGL_VERTEX_ARRAY_BINDING, (GLint *)&VAO);
if(live.name == 0)
gl.glBindVertexArray(m_GL->GetFakeVAO());
else
gl.glBindVertexArray(live.name);
for(GLuint i = 0; i < 16; i++)
{
VertexAttribInitialData &attrib = initialdata->VertexAttribs[i];
if(attrib.enabled)
gl.glEnableVertexAttribArray(i);
else
gl.glDisableVertexAttribArray(i);
gl.glVertexAttribBinding(i, attrib.vbslot);
if(attrib.size != 0)
{
if(attrib.type == eGL_DOUBLE)
gl.glVertexAttribLFormat(i, attrib.size, attrib.type, attrib.offset);
else if(attrib.integer == 0)
gl.glVertexAttribFormat(i, attrib.size, attrib.type, (GLboolean)attrib.normalized,
attrib.offset);
else
gl.glVertexAttribIFormat(i, attrib.size, attrib.type, attrib.offset);
}
VertexBufferInitialData &buf = initialdata->VertexBuffers[i];
gl.glBindVertexBuffer(i, buf.Buffer.name, (GLintptr)buf.Offset, (GLsizei)buf.Stride);
gl.glVertexBindingDivisor(i, buf.Divisor);
}
GLuint buffer = initialdata->ElementArrayBuffer.name;
gl.glBindBuffer(eGL_ELEMENT_ARRAY_BUFFER, buffer);
gl.glBindVertexArray(VAO);
}
}
else if(live.Namespace == eResRenderbuffer)
{
}
else
{
RDCERR("Unexpected type of resource requiring initial state");
}
}