mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-07-09 09:10:49 +00:00
adbf24da77
* If multiple shaders are attached to a program only use the latest set as they override anything that came before.
2302 lines
85 KiB
C++
2302 lines
85 KiB
C++
/******************************************************************************
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2017-2018 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"
|
|
|
|
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,
|
|
};
|
|
|
|
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);
|
|
if(ser.VersionAtLeast(0x1B))
|
|
{
|
|
SERIALISE_MEMBER(numVirtualSamples);
|
|
SERIALISE_MEMBER(numViews);
|
|
SERIALISE_MEMBER(startView);
|
|
}
|
|
else if(ser.IsReading())
|
|
{
|
|
el.numVirtualSamples = 1;
|
|
el.numViews = 1;
|
|
el.startView = 0;
|
|
}
|
|
SERIALISE_MEMBER(obj);
|
|
}
|
|
|
|
template <typename SerialiserType>
|
|
void DoSerialise(SerialiserType &ser, FramebufferInitialData &el)
|
|
{
|
|
SERIALISE_MEMBER(valid);
|
|
SERIALISE_MEMBER(Attachments);
|
|
SERIALISE_MEMBER(DrawBuffers);
|
|
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(minFilter);
|
|
SERIALISE_MEMBER(magFilter);
|
|
SERIALISE_MEMBER(seamless);
|
|
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;
|
|
}
|
|
|
|
void GLResourceManager::ContextPrepare_InitialState(GLResource res)
|
|
{
|
|
GLInitialContents initContents;
|
|
|
|
if(res.Namespace == eResFramebuffer)
|
|
{
|
|
FramebufferInitialData &data = initContents.fbo;
|
|
|
|
ContextPair &ctx = m_Driver->GetCtx();
|
|
|
|
RDCASSERT(!data.valid);
|
|
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);
|
|
|
|
if(HasExt[ARB_geometry_shader4])
|
|
GL.glGetNamedFramebufferAttachmentParameterivEXT(
|
|
res.name, attachment, eGL_FRAMEBUFFER_ATTACHMENT_LAYERED, &layered);
|
|
else
|
|
layered = 0;
|
|
|
|
if(layered == 0)
|
|
GL.glGetNamedFramebufferAttachmentParameterivEXT(
|
|
res.name, attachment, eGL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER, &a.layer);
|
|
|
|
if(HasExt[EXT_multisampled_render_to_texture])
|
|
GL.glGetNamedFramebufferAttachmentParameterivEXT(
|
|
res.name, attachment, eGL_FRAMEBUFFER_ATTACHMENT_TEXTURE_SAMPLES_EXT,
|
|
&a.numVirtualSamples);
|
|
|
|
if(HasExt[OVR_multiview])
|
|
{
|
|
GL.glGetNamedFramebufferAttachmentParameterivEXT(
|
|
res.name, attachment, eGL_FRAMEBUFFER_ATTACHMENT_TEXTURE_NUM_VIEWS_OVR, &a.numViews);
|
|
GL.glGetNamedFramebufferAttachmentParameterivEXT(
|
|
res.name, attachment, eGL_FRAMEBUFFER_ATTACHMENT_TEXTURE_BASE_VIEW_INDEX_OVR,
|
|
&a.startView);
|
|
}
|
|
}
|
|
|
|
a.layered = (layered != 0);
|
|
a.obj = (type == eGL_RENDERBUFFER) ? RenderbufferRes(ctx, object) : TextureRes(ctx, object);
|
|
|
|
if(type != eGL_RENDERBUFFER)
|
|
{
|
|
WrappedOpenGL::TextureData &details = m_Driver->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);
|
|
}
|
|
}
|
|
}
|
|
|
|
GLuint maxDraws = 0;
|
|
GL.glGetIntegerv(eGL_MAX_DRAW_BUFFERS, (GLint *)&maxDraws);
|
|
|
|
for(GLuint i = 0; i < (GLuint)ARRAY_COUNT(data.DrawBuffers); i++)
|
|
{
|
|
if(i < maxDraws)
|
|
GL.glGetIntegerv(GLenum(eGL_DRAW_BUFFER0 + i), (GLint *)&data.DrawBuffers[i]);
|
|
else
|
|
data.DrawBuffers[i] = eGL_COLOR_ATTACHMENT0;
|
|
}
|
|
|
|
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 = initContents.pipe;
|
|
|
|
RDCASSERT(!data.valid);
|
|
data.valid = true;
|
|
|
|
// programs are shared
|
|
void *shareGroup = m_Driver->GetCtx().shareGroup;
|
|
|
|
for(GLuint i = 0; i < (GLuint)ARRAY_COUNT(data.programs); i++)
|
|
{
|
|
data.programs[i].Namespace = eResProgram;
|
|
data.programs[i].ContextShareGroup = shareGroup;
|
|
}
|
|
|
|
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 = initContents.xfb;
|
|
|
|
RDCASSERT(!data.valid);
|
|
data.valid = true;
|
|
|
|
ContextPair &ctx = m_Driver->GetCtx();
|
|
|
|
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(ctx, 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 = initContents.vao;
|
|
|
|
RDCASSERT(!data.valid);
|
|
data.valid = true;
|
|
|
|
ContextPair &ctx = m_Driver->GetCtx();
|
|
|
|
GLuint prevVAO = 0;
|
|
GL.glGetIntegerv(eGL_VERTEX_ARRAY_BINDING, (GLint *)&prevVAO);
|
|
|
|
GL.glBindVertexArray(res.name);
|
|
|
|
for(GLuint i = 0; i < 16; i++)
|
|
{
|
|
GLuint buffer = GetBoundVertexBuffer(i);
|
|
|
|
data.VertexBuffers[i].Buffer = BufferRes(ctx, buffer);
|
|
}
|
|
|
|
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_ARRAY_TYPE, (GLint *)&data.VertexAttribs[i].type);
|
|
GL.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_ARRAY_NORMALIZED,
|
|
(GLint *)&data.VertexAttribs[i].normalized);
|
|
|
|
// no extension for this, it just appeared in GL & GLES 3.0, along with glVertexAttribIPointer
|
|
if(GLCoreVersion >= 3.0)
|
|
GL.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_ARRAY_INTEGER,
|
|
(GLint *)&data.VertexAttribs[i].integer);
|
|
else
|
|
data.VertexAttribs[i].integer = 0;
|
|
|
|
GL.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_ARRAY_SIZE, (GLint *)&data.VertexAttribs[i].size);
|
|
|
|
if(HasExt[ARB_vertex_attrib_binding])
|
|
{
|
|
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);
|
|
|
|
GL.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_RELATIVE_OFFSET,
|
|
(GLint *)&data.VertexAttribs[i].offset);
|
|
GL.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_BINDING, (GLint *)&data.VertexAttribs[i].vbslot);
|
|
}
|
|
else
|
|
{
|
|
GL.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_ARRAY_STRIDE,
|
|
(GLint *)&data.VertexBuffers[i].Stride);
|
|
GL.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_ARRAY_DIVISOR,
|
|
(GLint *)&data.VertexBuffers[i].Divisor);
|
|
data.VertexAttribs[i].vbslot = i;
|
|
data.VertexBuffers[i].Offset = 0;
|
|
|
|
void *ptr = NULL;
|
|
GL.glGetVertexAttribPointerv(i, eGL_VERTEX_ATTRIB_ARRAY_POINTER, &ptr);
|
|
|
|
data.VertexAttribs[i].offset = (uint32_t)(uintptr_t)ptr;
|
|
}
|
|
|
|
// if no buffer is bound, replace any non-zero offset with a marker value. This makes captures
|
|
// more deterministic and ensures that if we ever try to use the invalid offset/pointer then
|
|
// we crash with a known value.
|
|
if(data.VertexBuffers[data.VertexAttribs[i].vbslot].Buffer.name == 0 &&
|
|
data.VertexAttribs[i].offset > 0)
|
|
data.VertexAttribs[i].offset = 0xDEADBEEF;
|
|
|
|
if(data.VertexBuffers[i].Buffer.name == 0 && data.VertexBuffers[i].Offset > 0)
|
|
data.VertexBuffers[i].Offset = 0xDEADBEEF;
|
|
}
|
|
|
|
GLuint buffer = 0;
|
|
GL.glGetIntegerv(eGL_ELEMENT_ARRAY_BUFFER_BINDING, (GLint *)&buffer);
|
|
data.ElementArrayBuffer = BufferRes(ctx, buffer);
|
|
|
|
GL.glBindVertexArray(prevVAO);
|
|
}
|
|
|
|
SetInitialContents(GetID(res), initContents);
|
|
}
|
|
|
|
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);
|
|
|
|
if(res.Namespace == eResBuffer)
|
|
{
|
|
// get the length of the buffer
|
|
uint32_t length = 4;
|
|
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)RDCMAX(length, 4U), NULL, eGL_STATIC_READ);
|
|
|
|
// bind the live buffer for copying
|
|
GL.glBindBuffer(eGL_COPY_READ_BUFFER, res.name);
|
|
|
|
// do the actual copy
|
|
if(length > 0)
|
|
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, GLInitialContents(GLResource(res.ContextShareGroup, eResBuffer, buf), length));
|
|
}
|
|
else if(res.Namespace == eResProgram)
|
|
{
|
|
WriteSerialiser ser(new StreamWriter(4 * 1024), Ownership::Stream);
|
|
|
|
ser.SetChunkMetadataRecording(m_Driver->GetSerialiser().GetChunkMetadataRecording());
|
|
|
|
SCOPED_SERIALISE_CHUNK(SystemChunk::InitialContents);
|
|
|
|
SERIALISE_ELEMENT(Id).TypedAs("GLResource");
|
|
SERIALISE_ELEMENT(res.Namespace);
|
|
|
|
SerialiseProgramBindings(ser, CaptureState::ActiveCapturing, res.name);
|
|
SerialiseProgramUniforms(ser, CaptureState::ActiveCapturing, res.name, NULL);
|
|
|
|
SetInitialChunk(Id, scope.Get());
|
|
}
|
|
else if(res.Namespace == eResTexture)
|
|
{
|
|
PrepareTextureInitialContents(Id, Id, res);
|
|
}
|
|
else if(res.Namespace == eResFramebuffer)
|
|
{
|
|
// 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 a correct context.
|
|
// The correct context depends on whether the object is shared or not - if it's shared, any
|
|
// context in the same share group will do, otherwise it must be precisely the right 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.
|
|
ContextPair &ctx = m_Driver->GetCtx();
|
|
if(res.ContextShareGroup != ctx.ctx && res.ContextShareGroup != ctx.shareGroup)
|
|
{
|
|
m_Driver->QueuePrepareInitialState(res);
|
|
}
|
|
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).
|
|
ContextPrepare_InitialState(res);
|
|
}
|
|
}
|
|
else if(res.Namespace == eResProgramPipe)
|
|
{
|
|
// queue initial state fetching if we're not on the right context, see above in FBOs for more
|
|
// explanation of this.
|
|
ContextPair &ctx = m_Driver->GetCtx();
|
|
if(res.ContextShareGroup != ctx.ctx && res.ContextShareGroup != ctx.shareGroup)
|
|
{
|
|
m_Driver->QueuePrepareInitialState(res);
|
|
}
|
|
else
|
|
{
|
|
ContextPrepare_InitialState(res);
|
|
}
|
|
}
|
|
else if(res.Namespace == eResFeedback)
|
|
{
|
|
// queue initial state fetching if we're not on the right context, see above in FBOs for more
|
|
// explanation of this.
|
|
ContextPair &ctx = m_Driver->GetCtx();
|
|
if(res.ContextShareGroup != ctx.ctx && res.ContextShareGroup != ctx.shareGroup)
|
|
{
|
|
m_Driver->QueuePrepareInitialState(res);
|
|
}
|
|
else
|
|
{
|
|
ContextPrepare_InitialState(res);
|
|
}
|
|
}
|
|
else if(res.Namespace == eResVertexArray)
|
|
{
|
|
// queue initial state fetching if we're not on the right context, see above in FBOs for more
|
|
// explanation of this.
|
|
ContextPair &ctx = m_Driver->GetCtx();
|
|
if(res.ContextShareGroup != ctx.ctx && res.ContextShareGroup != ctx.shareGroup)
|
|
{
|
|
m_Driver->QueuePrepareInitialState(res);
|
|
}
|
|
else
|
|
{
|
|
ContextPrepare_InitialState(res);
|
|
}
|
|
}
|
|
else if(res.Namespace == eResRenderbuffer)
|
|
{
|
|
//
|
|
}
|
|
else
|
|
{
|
|
RDCERR("Unexpected type of resource requiring initial state");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void GLResourceManager::CreateTextureImage(GLuint tex, GLenum internalFormat,
|
|
GLenum internalFormatHint, GLenum textype, GLint dim,
|
|
GLint width, GLint height, GLint depth, GLint samples,
|
|
int mips)
|
|
{
|
|
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);
|
|
GL.glTextureParameteriEXT(tex, textype, eGL_TEXTURE_MIN_FILTER, eGL_NEAREST);
|
|
GL.glTextureParameteriEXT(tex, textype, eGL_TEXTURE_MAG_FILTER, eGL_NEAREST);
|
|
GL.glTextureParameteriEXT(tex, textype, eGL_TEXTURE_WRAP_S, eGL_CLAMP_TO_EDGE);
|
|
GL.glTextureParameteriEXT(tex, textype, eGL_TEXTURE_WRAP_T, eGL_CLAMP_TO_EDGE);
|
|
|
|
bool isCompressed = IsCompressedFormat(internalFormat);
|
|
|
|
GLenum baseFormat = eGL_RGBA;
|
|
GLenum dataType = internalFormatHint != eGL_NONE ? internalFormatHint : eGL_UNSIGNED_BYTE;
|
|
if(!isCompressed)
|
|
{
|
|
baseFormat = GetBaseFormat(internalFormat);
|
|
|
|
if(internalFormatHint == eGL_NONE)
|
|
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);
|
|
}
|
|
}
|
|
|
|
if(IsCaptureMode(m_State))
|
|
{
|
|
// register this texture and set up its texture details, so it's available for emulation
|
|
// readback.
|
|
GLResource res = TextureRes(m_Driver->GetCtx(), tex);
|
|
ResourceId id = RegisterResource(res);
|
|
|
|
WrappedOpenGL::TextureData &details = m_Driver->m_Textures[id];
|
|
|
|
details.resource = res;
|
|
details.curType = textype;
|
|
details.dimension = dim;
|
|
details.emulated = details.view = false;
|
|
details.width = width;
|
|
details.height = height;
|
|
details.depth = depth;
|
|
details.samples = samples;
|
|
details.creationFlags = TextureCategory::NoFlags;
|
|
details.internalFormat = internalFormat;
|
|
details.mipsValid = (1 << mips) - 1;
|
|
}
|
|
}
|
|
|
|
void GLResourceManager::PrepareTextureInitialContents(ResourceId liveid, ResourceId origid,
|
|
GLResource res)
|
|
{
|
|
WrappedOpenGL::TextureData &details = m_Driver->m_Textures[liveid];
|
|
|
|
GLInitialContents initContents;
|
|
|
|
TextureStateInitialData &state = initContents.tex;
|
|
|
|
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.
|
|
}
|
|
else if(details.curType != eGL_TEXTURE_BUFFER)
|
|
{
|
|
GLenum binding = TextureBinding(details.curType);
|
|
|
|
state.mips = GetNumMips(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(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)
|
|
{
|
|
if(HasExt[EXT_texture_sRGB_decode])
|
|
GL.glGetTextureParameterivEXT(res.name, details.curType, eGL_TEXTURE_SRGB_DECODE_EXT,
|
|
(GLint *)&state.srgbDecode);
|
|
else
|
|
state.srgbDecode = eGL_DECODE_EXT;
|
|
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);
|
|
|
|
// technically border color has been in since GL 1.0, but since this extension was really
|
|
// early and dovetails nicely with OES_texture_border_color which added both border colors and
|
|
// clamping, we check it.
|
|
if(HasExt[ARB_texture_border_clamp])
|
|
GL.glGetTextureParameterfvEXT(res.name, details.curType, eGL_TEXTURE_BORDER_COLOR,
|
|
&state.border[0]);
|
|
else
|
|
state.border[0] = state.border[1] = state.border[2] = state.border[3] = 1.0f;
|
|
|
|
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(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.internalFormatHint, 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(false);
|
|
unpack.Fetch(false);
|
|
|
|
ResetPixelPackState(false, 1);
|
|
ResetPixelUnpackState(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);
|
|
|
|
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(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(false);
|
|
unpack.Apply(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);
|
|
}
|
|
|
|
initContents.resource = GLResource(res.ContextShareGroup, eResTexture, tex);
|
|
}
|
|
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 = GLResource(res.ContextShareGroup, eResBuffer, 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, initContents);
|
|
}
|
|
|
|
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).bufferLength + (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).TypedAs("GLResource");
|
|
SERIALISE_ELEMENT(res.Namespace);
|
|
|
|
SerialiseProgramBindings(ser, CaptureState::ActiveCapturing, res.name);
|
|
SerialiseProgramUniforms(ser, CaptureState::ActiveCapturing, 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 = GetInitialContents(resid).tex;
|
|
|
|
// 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_Driver->GetState();
|
|
|
|
SERIALISE_ELEMENT_LOCAL(Id, GetID(res)).TypedAs("GLResource");
|
|
SERIALISE_ELEMENT_LOCAL(Type, res.Namespace);
|
|
GLInitialContents initContents = GetInitialContents(Id);
|
|
|
|
if(IsReplayingAndReading())
|
|
{
|
|
if(HasLiveResource(Id))
|
|
res = GetLiveResource(Id);
|
|
else
|
|
res = GLResource(MakeNullResource);
|
|
|
|
m_Driver->AddResourceCurChunk(Id);
|
|
}
|
|
|
|
if(Type == eResBuffer)
|
|
{
|
|
uint32_t BufferContentsSize = 0;
|
|
byte *BufferContents = NULL;
|
|
|
|
if(ser.IsWriting())
|
|
{
|
|
res = initContents.resource;
|
|
BufferContentsSize = initContents.bufferLength;
|
|
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)RDCMAX(BufferContentsSize, 4U), NULL,
|
|
eGL_STATIC_DRAW);
|
|
BufferContents = (byte *)GL.glMapNamedBufferEXT(res.name, eGL_WRITE_ONLY);
|
|
|
|
SetInitialContents(
|
|
Id, GLInitialContents(BufferRes(m_Driver->GetCtx(), res.name), BufferContentsSize));
|
|
}
|
|
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_Driver->m_Programs[GetLiveID(Id)];
|
|
|
|
GLuint initProg = GL.glCreateProgram();
|
|
|
|
uint32_t numShaders = 0;
|
|
|
|
std::vector<std::string> vertexOutputs;
|
|
for(size_t i = 0; i < ARRAY_COUNT(details.stageShaders); i++)
|
|
{
|
|
if(details.stageShaders[i] == ResourceId())
|
|
continue;
|
|
|
|
numShaders++;
|
|
|
|
const auto &shadDetails = m_Driver->m_Shaders[details.stageShaders[i]];
|
|
|
|
GLuint shad = GL.glCreateShader(shadDetails.type);
|
|
|
|
if(shadDetails.type == eGL_VERTEX_SHADER)
|
|
{
|
|
for(const SigParameter &sig : shadDetails.reflection.outputSignature)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
if(!shadDetails.sources.empty())
|
|
{
|
|
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);
|
|
}
|
|
else if(!shadDetails.spirvWords.empty())
|
|
{
|
|
GL.glShaderBinary(1, &shad, eGL_SHADER_BINARY_FORMAT_SPIR_V, shadDetails.spirvWords.data(),
|
|
(GLsizei)shadDetails.spirvWords.size() * sizeof(uint32_t));
|
|
|
|
GL.glSpecializeShader(shad, shadDetails.entryPoint.c_str(),
|
|
(GLuint)shadDetails.specIDs.size(), shadDetails.specIDs.data(),
|
|
shadDetails.specValues.data());
|
|
|
|
GL.glAttachShader(initProg, shad);
|
|
GL.glDeleteShader(shad);
|
|
}
|
|
else
|
|
{
|
|
RDCERR("Unexpectedly empty shader in program initial state!");
|
|
}
|
|
}
|
|
|
|
// 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(numShaders == 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, bindingsProgram);
|
|
|
|
// re-link the program to set the new attrib bindings
|
|
if(IsReplayingAndReading() && !ser.IsErrored())
|
|
GL.glLinkProgram(bindingsProgram);
|
|
|
|
SerialiseProgramUniforms(ser, m_State, uniformsProgram, translationTable);
|
|
|
|
SERIALISE_CHECK_READ_ERRORS();
|
|
|
|
if(IsReplayingAndReading())
|
|
{
|
|
// see above for why we're copying this back
|
|
CopyProgramUniforms(uniformsProgram, bindingsProgram);
|
|
|
|
SetInitialContents(Id, GLInitialContents(ProgramRes(m_Driver->GetCtx(), bindingsProgram), 0));
|
|
}
|
|
}
|
|
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(false);
|
|
unpack.Fetch(false);
|
|
|
|
ResetPixelPackState(false, 1);
|
|
ResetPixelUnpackState(false, 1);
|
|
}
|
|
|
|
// serialise the texture metadata which was fetched during state preparation
|
|
TextureStateInitialData &TextureState = initContents.tex;
|
|
|
|
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_Driver->m_Textures[GetID(res)];
|
|
|
|
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.
|
|
}
|
|
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(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(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, details.internalFormatHint,
|
|
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 = initContents.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)
|
|
{
|
|
size_t startOffs =
|
|
IsCubeFace(targets[trg]) ? CubeTargetIndex(targets[trg]) * size : 0;
|
|
|
|
details.compressedData[i].resize(startOffs + size);
|
|
memcpy(details.compressedData[i].data() + startOffs, 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);
|
|
|
|
initContents.resource = TextureRes(m_Driver->GetCtx(), tex);
|
|
}
|
|
|
|
if(IsReplayingAndReading() && !ser.IsErrored())
|
|
{
|
|
SetInitialContents(Id, initContents);
|
|
}
|
|
}
|
|
|
|
// 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(false);
|
|
unpack.Apply(false);
|
|
}
|
|
|
|
SERIALISE_CHECK_READ_ERRORS();
|
|
}
|
|
else if(Type == eResFramebuffer)
|
|
{
|
|
FramebufferInitialData &FramebufferState = initContents.fbo;
|
|
|
|
SERIALISE_ELEMENT(FramebufferState);
|
|
|
|
SERIALISE_CHECK_READ_ERRORS();
|
|
|
|
if(IsReplayingAndReading())
|
|
{
|
|
byte *blob = AllocAlignedBuffer(sizeof(FramebufferState));
|
|
memcpy(blob, &FramebufferState, sizeof(FramebufferState));
|
|
|
|
SetInitialContents(Id, initContents);
|
|
}
|
|
}
|
|
else if(Type == eResFeedback)
|
|
{
|
|
FeedbackInitialData &TransformFeedbackState = initContents.xfb;
|
|
|
|
SERIALISE_ELEMENT(TransformFeedbackState);
|
|
|
|
SERIALISE_CHECK_READ_ERRORS();
|
|
|
|
if(IsReplayingAndReading())
|
|
{
|
|
byte *blob = AllocAlignedBuffer(sizeof(TransformFeedbackState));
|
|
memcpy(blob, &TransformFeedbackState, sizeof(TransformFeedbackState));
|
|
|
|
SetInitialContents(Id, initContents);
|
|
}
|
|
}
|
|
else if(Type == eResProgramPipe)
|
|
{
|
|
PipelineInitialData &ProgramPipelineState = initContents.pipe;
|
|
|
|
SERIALISE_ELEMENT(ProgramPipelineState);
|
|
|
|
SERIALISE_CHECK_READ_ERRORS();
|
|
|
|
if(IsReplayingAndReading())
|
|
{
|
|
byte *blob = AllocAlignedBuffer(sizeof(ProgramPipelineState));
|
|
memcpy(blob, &ProgramPipelineState, sizeof(ProgramPipelineState));
|
|
|
|
SetInitialContents(Id, initContents);
|
|
}
|
|
}
|
|
else if(Type == eResVertexArray)
|
|
{
|
|
VAOInitialData &VAOState = initContents.vao;
|
|
|
|
SERIALISE_ELEMENT(VAOState);
|
|
|
|
SERIALISE_CHECK_READ_ERRORS();
|
|
|
|
if(IsReplayingAndReading())
|
|
{
|
|
byte *blob = AllocAlignedBuffer(sizeof(VAOState));
|
|
memcpy(blob, &VAOState, sizeof(VAOState));
|
|
|
|
SetInitialContents(Id, initContents);
|
|
}
|
|
}
|
|
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)
|
|
{
|
|
ContextPrepare_InitialState(live);
|
|
}
|
|
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, GLInitialContents initial)
|
|
{
|
|
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
|
|
if(initial.bufferLength > 0)
|
|
GL.glCopyBufferSubData(eGL_COPY_READ_BUFFER, eGL_COPY_WRITE_BUFFER, 0, 0,
|
|
(GLsizeiptr)initial.bufferLength);
|
|
|
|
// 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_Driver->m_Textures[Id];
|
|
|
|
const TextureStateInitialData &state = initial.tex;
|
|
|
|
if(details.curType != eGL_TEXTURE_BUFFER)
|
|
{
|
|
GLuint tex = initial.resource.name;
|
|
|
|
if(initial.resource != GLResource(MakeNullResource) && tex != 0)
|
|
{
|
|
int mips = GetNumMips(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(false);
|
|
unpack.Fetch(false);
|
|
|
|
ResetPixelPackState(false, 1);
|
|
ResetPixelUnpackState(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(false);
|
|
unpack.Apply(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(live.name, details.curType, state.swizzle);
|
|
}
|
|
|
|
if(!ms)
|
|
{
|
|
if(HasExt[EXT_texture_sRGB_decode])
|
|
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]);
|
|
|
|
// see fetch in PrepareTextureInitialContents
|
|
if(HasExt[ARB_texture_border_clamp])
|
|
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_Driver->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_Driver->m_Programs[Id];
|
|
|
|
if(prog.stageShaders[0] != ResourceId())
|
|
CopyProgramAttribBindings(initial.resource.name, live.name,
|
|
&m_Driver->m_Shaders[prog.stageShaders[0]].reflection);
|
|
|
|
if(prog.stageShaders[4] != ResourceId())
|
|
CopyProgramFragDataBindings(initial.resource.name, live.name,
|
|
&m_Driver->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(initial.resource.name, live.name);
|
|
}
|
|
else if(live.Namespace == eResFramebuffer)
|
|
{
|
|
const FramebufferInitialData &data = initial.fbo;
|
|
|
|
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++)
|
|
{
|
|
const 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_Driver->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)
|
|
{
|
|
if(a.numViews > 1)
|
|
{
|
|
if(a.numVirtualSamples > 1)
|
|
{
|
|
GL.glFramebufferTextureMultisampleMultiviewOVR(eGL_DRAW_FRAMEBUFFER, attachment,
|
|
obj, a.level, a.numVirtualSamples,
|
|
a.startView, a.numViews);
|
|
}
|
|
else
|
|
{
|
|
GL.glFramebufferTextureMultiviewOVR(eGL_DRAW_FRAMEBUFFER, attachment, obj,
|
|
a.level, a.startView, a.numViews);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GL.glFramebufferTextureLayer(eGL_DRAW_FRAMEBUFFER, attachment, obj, a.level, a.layer);
|
|
}
|
|
}
|
|
else if(a.numVirtualSamples > 1)
|
|
{
|
|
GL.glFramebufferTexture2DMultisampleEXT(eGL_DRAW_FRAMEBUFFER, attachment,
|
|
details.curType, obj, a.level,
|
|
a.numVirtualSamples);
|
|
}
|
|
else
|
|
{
|
|
RDCASSERT(a.layer == 0);
|
|
GL.glNamedFramebufferTextureEXT(live.name, attachment, obj, a.level);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GL.glNamedFramebufferTextureEXT(live.name, attachment, obj, a.level);
|
|
}
|
|
}
|
|
}
|
|
|
|
GLenum drawbuffers[8];
|
|
memcpy(drawbuffers, data.DrawBuffers, sizeof(drawbuffers));
|
|
RDCCOMPILE_ASSERT(sizeof(drawbuffers) == sizeof(data.DrawBuffers),
|
|
"Update drawbuffers array");
|
|
|
|
// set invalid caps to GL_COLOR_ATTACHMENT0
|
|
for(int i = 0; i < (int)ARRAY_COUNT(drawbuffers); i++)
|
|
if(drawbuffers[i] == eGL_BACK || drawbuffers[i] == eGL_FRONT)
|
|
drawbuffers[i] = eGL_COLOR_ATTACHMENT0;
|
|
|
|
GLenum readbuffer = data.ReadBuffer;
|
|
if(readbuffer == eGL_BACK || readbuffer == eGL_FRONT)
|
|
readbuffer = eGL_COLOR_ATTACHMENT0;
|
|
|
|
GLuint maxDraws = 0;
|
|
GL.glGetIntegerv(eGL_MAX_DRAW_BUFFERS, (GLint *)&maxDraws);
|
|
|
|
GL.glDrawBuffers(RDCMIN(maxDraws, (GLuint)ARRAY_COUNT(drawbuffers)), drawbuffers);
|
|
|
|
GL.glReadBuffer(readbuffer);
|
|
|
|
GL.glBindFramebuffer(eGL_DRAW_FRAMEBUFFER, prevdraw);
|
|
GL.glBindFramebuffer(eGL_READ_FRAMEBUFFER, prevread);
|
|
}
|
|
}
|
|
else if(live.Namespace == eResFeedback)
|
|
{
|
|
const FeedbackInitialData &data = initial.xfb;
|
|
|
|
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)
|
|
{
|
|
const PipelineInitialData &data = initial.pipe;
|
|
|
|
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)
|
|
{
|
|
const VAOInitialData &data = initial.vao;
|
|
|
|
if(data.valid)
|
|
{
|
|
GLuint VAO = 0;
|
|
GL.glGetIntegerv(eGL_VERTEX_ARRAY_BINDING, (GLint *)&VAO);
|
|
|
|
GL.glBindVertexArray(live.name);
|
|
|
|
for(GLuint i = 0; i < 16; i++)
|
|
{
|
|
const VertexAttribInitialData &attrib = data.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);
|
|
}
|
|
|
|
const VertexBufferInitialData &buf = data.VertexBuffers[i];
|
|
|
|
GL.glBindVertexBuffer(i, buf.Buffer.name, (GLintptr)buf.Offset, (GLsizei)buf.Stride);
|
|
GL.glVertexBindingDivisor(i, buf.Divisor);
|
|
}
|
|
|
|
GLuint buffer = data.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");
|
|
}
|
|
}
|