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

3985 lines
136 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2019-2021 Baldur Karlsson
* Copyright (c) 2014 Crytek
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#include "gl_replay.h"
#include "core/settings.h"
#include "driver/ihv/amd/amd_counters.h"
#include "driver/ihv/arm/arm_counters.h"
#include "driver/ihv/intel/intel_gl_counters.h"
#include "maths/matrix.h"
#include "serialise/rdcfile.h"
#include "strings/string_utils.h"
#include "gl_driver.h"
#include "gl_resources.h"
#define OPENGL 1
#include "data/glsl/glsl_ubos_cpp.h"
RDOC_CONFIG(bool, OpenGL_HardwareCounters, true,
"Enable support for IHV-specific hardware counters on OpenGL.");
static const char *SPIRVDisassemblyTarget = "SPIR-V (RenderDoc)";
GLReplay::GLReplay(WrappedOpenGL *d)
{
m_pDriver = d;
if(RenderDoc::Inst().GetCrashHandler())
RenderDoc::Inst().GetCrashHandler()->RegisterMemoryRegion(this, sizeof(GLReplay));
m_Proxy = false;
m_Degraded = false;
RDCEraseEl(m_ReplayCtx);
m_DebugCtx = NULL;
m_DebugID = 0;
m_OutputWindowID = 1;
RDCEraseEl(m_GetTexturePrevData);
RDCEraseEl(m_DriverInfo);
}
void GLReplay::Shutdown()
{
SAFE_DELETE(m_pAMDCounters);
SAFE_DELETE(m_pIntelCounters);
SAFE_DELETE(m_pARMCounters);
DeleteDebugData();
DestroyOutputWindow(m_DebugID);
CloseReplayContext();
// clean up cached GetTextureData allocations
for(size_t i = 0; i < ARRAY_COUNT(m_GetTexturePrevData); i++)
{
delete[] m_GetTexturePrevData[i];
m_GetTexturePrevData[i] = NULL;
}
delete m_pDriver;
}
ReplayStatus GLReplay::ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers)
{
MakeCurrentReplayContext(&m_ReplayCtx);
return m_pDriver->ReadLogInitialisation(rdc, storeStructuredBuffers);
}
void GLReplay::ReplayLog(uint32_t endEventID, ReplayLogType replayType)
{
MakeCurrentReplayContext(&m_ReplayCtx);
m_pDriver->ReplayLog(0, endEventID, replayType);
// clear array cache
for(size_t i = 0; i < ARRAY_COUNT(m_GetTexturePrevData); i++)
{
delete[] m_GetTexturePrevData[i];
m_GetTexturePrevData[i] = NULL;
}
}
const SDFile &GLReplay::GetStructuredFile()
{
return m_pDriver->GetStructuredFile();
}
rdcarray<uint32_t> GLReplay::GetPassEvents(uint32_t eventId)
{
rdcarray<uint32_t> passEvents;
const DrawcallDescription *draw = m_pDriver->GetDrawcall(eventId);
const DrawcallDescription *start = draw;
while(start && start->previous && !(start->previous->flags & DrawFlags::Clear))
{
const DrawcallDescription *prev = start->previous;
if(start->outputs != prev->outputs || start->depthOut != prev->depthOut)
break;
start = prev;
}
while(start)
{
if(start == draw)
break;
if(start->flags & DrawFlags::Drawcall)
passEvents.push_back(start->eventId);
start = start->next;
}
return passEvents;
}
rdcarray<WindowingSystem> GLReplay::GetSupportedWindowSystems()
{
rdcarray<WindowingSystem> ret;
#if ENABLED(RDOC_LINUX)
#if ENABLED(RDOC_WAYLAND)
// if wayland is supported and a display is configured, we *must* get wayland surfaces to render
// on
if(RenderDoc::Inst().GetGlobalEnvironment().waylandDisplay)
{
ret.push_back(WindowingSystem::Wayland);
}
else
#endif
{
// only Xlib supported for GLX. We can't report XCB here since we need the Display, and that
// can't be obtained from XCB. The application is free to use XCB internally but it would have
// to create a hybrid and initialise XCB out of Xlib, to be able to provide the display and
// drawable to us.
ret.push_back(WindowingSystem::Xlib);
}
#elif ENABLED(RDOC_ANDROID)
ret.push_back(WindowingSystem::Android);
#elif ENABLED(RDOC_APPLE)
ret.push_back(WindowingSystem::MacOS);
#elif ENABLED(RDOC_WIN32)
ret.push_back(WindowingSystem::Win32);
#endif
return ret;
}
ResourceId GLReplay::GetLiveID(ResourceId id)
{
if(!m_pDriver->GetResourceManager()->HasLiveResource(id))
return ResourceId();
return m_pDriver->GetResourceManager()->GetLiveID(id);
}
rdcarray<GPUDevice> GLReplay::GetAvailableGPUs()
{
// GL doesn't support multiple GPUs, return an empty list
return {};
}
APIProperties GLReplay::GetAPIProperties()
{
APIProperties ret = m_pDriver->APIProps;
ret.pipelineType = GraphicsAPI::OpenGL;
ret.localRenderer = GraphicsAPI::OpenGL;
ret.degraded = m_Degraded;
ret.vendor = m_DriverInfo.vendor;
ret.shadersMutable = true;
return ret;
}
ResourceDescription &GLReplay::GetResourceDesc(ResourceId id)
{
auto it = m_ResourceIdx.find(id);
if(it == m_ResourceIdx.end())
{
m_ResourceIdx[id] = m_Resources.size();
m_Resources.push_back(ResourceDescription());
m_Resources.back().resourceId = id;
return m_Resources.back();
}
return m_Resources[it->second];
}
rdcarray<ResourceDescription> GLReplay::GetResources()
{
return m_Resources;
}
void GLReplay::SetReplayData(GLWindowingData data)
{
m_ReplayCtx = data;
m_pDriver->RegisterReplayContext(m_ReplayCtx, NULL, true, true);
m_pDriver->RegisterDebugCallback();
InitDebugData();
if(!HasDebugContext())
return;
if(!m_Proxy && OpenGL_HardwareCounters())
{
AMDCounters *countersAMD = NULL;
IntelGlCounters *countersIntel = NULL;
ARMCounters *countersARM = NULL;
bool isMesa = false;
// try to identify mesa - don't enable any IHV counters when running mesa.
{
WrappedOpenGL &drv = *m_pDriver;
const char *version = (const char *)drv.glGetString(eGL_VERSION);
const char *vendor = (const char *)drv.glGetString(eGL_VENDOR);
const char *renderer = (const char *)drv.glGetString(eGL_RENDERER);
for(rdcstr haystack : {strlower(version), strlower(vendor), strlower(renderer)})
{
haystack = " " + haystack + " ";
// the version should always contain 'mesa', but it's also commonly present in either vendor
// or renderer - except for nouveau which we look for separately
for(const char *needle : {" mesa ", "nouveau"})
{
if(haystack.contains(needle))
{
isMesa = true;
break;
}
}
if(isMesa)
break;
}
}
if(isMesa)
{
if(m_DriverInfo.vendor == GPUVendor::Intel)
{
RDCLOG("Intel GPU detected - trying to initialise Intel GL counters");
countersIntel = new IntelGlCounters();
}
else
RDCLOG("Non Intel Mesa driver detected - skipping IHV counter initialisation");
}
else
{
if(m_DriverInfo.vendor == GPUVendor::Intel)
{
RDCLOG("Intel GPU detected - trying to initialise Intel GL counters");
countersIntel = new IntelGlCounters();
}
else if(m_DriverInfo.vendor == GPUVendor::AMD)
{
RDCLOG("AMD GPU detected - trying to initialise AMD counters");
countersAMD = new AMDCounters();
}
else if(m_DriverInfo.vendor == GPUVendor::ARM)
{
RDCLOG("ARM Mali GPU detected - trying to initialise ARM counters");
countersARM = new ARMCounters();
}
else
{
RDCLOG("%s GPU detected - no counters available", ToStr(m_DriverInfo.vendor).c_str());
}
}
if(countersAMD && countersAMD->Init(AMDCounters::ApiType::Ogl, m_ReplayCtx.ctx))
{
m_pAMDCounters = countersAMD;
}
else
{
delete countersAMD;
m_pAMDCounters = NULL;
}
if(countersIntel && countersIntel->Init())
{
m_pIntelCounters = countersIntel;
}
else
{
delete countersIntel;
m_pIntelCounters = NULL;
}
if(countersARM && countersARM->Init())
{
m_pARMCounters = countersARM;
}
else
{
delete countersARM;
m_pARMCounters = NULL;
}
}
}
void GLReplay::GetBufferData(ResourceId buff, uint64_t offset, uint64_t len, bytebuf &ret)
{
if(m_pDriver->m_Buffers.find(buff) == m_pDriver->m_Buffers.end())
{
RDCWARN("Requesting data for non-existant buffer %s", ToStr(buff).c_str());
ret.clear();
return;
}
auto &buf = m_pDriver->m_Buffers[buff];
uint64_t bufsize = buf.size;
if(offset >= bufsize)
{
// can't read past the end of the buffer, return empty
return;
}
if(len == 0 || len > bufsize)
{
len = bufsize;
}
if(offset + len > bufsize)
{
RDCWARN("Attempting to read off the end of the buffer (%llu %llu). Will be clamped (%llu)",
offset, len, bufsize);
len = RDCMIN(len, bufsize - offset);
}
ret.resize((size_t)len);
WrappedOpenGL &drv = *m_pDriver;
GLuint oldbuf = 0;
drv.glGetIntegerv(eGL_COPY_READ_BUFFER_BINDING, (GLint *)&oldbuf);
drv.glBindBuffer(eGL_COPY_READ_BUFFER, buf.resource.name);
drv.glGetBufferSubData(eGL_COPY_READ_BUFFER, (GLintptr)offset, (GLsizeiptr)len, &ret[0]);
drv.glBindBuffer(eGL_COPY_READ_BUFFER, oldbuf);
}
void GLReplay::CacheTexture(ResourceId id)
{
if(m_CachedTextures.find(id) != m_CachedTextures.end())
return;
TextureDescription tex = {};
MakeCurrentReplayContext(&m_ReplayCtx);
auto &res = m_pDriver->m_Textures[id];
WrappedOpenGL &drv = *m_pDriver;
tex.resourceId = m_pDriver->GetResourceManager()->GetOriginalID(id);
if(res.resource.Namespace == eResUnknown || res.curType == eGL_NONE)
{
if(res.resource.Namespace == eResUnknown)
RDCERR("Details for invalid texture id %s requested", ToStr(id).c_str());
tex.format = ResourceFormat();
tex.dimension = 1;
tex.type = TextureType::Unknown;
tex.width = tex.height = tex.depth = 1;
tex.cubemap = false;
tex.mips = 1;
tex.arraysize = 1;
tex.creationFlags = TextureCategory::NoFlags;
tex.msQual = 0;
tex.msSamp = 1;
tex.byteSize = 1;
m_CachedTextures[id] = tex;
return;
}
if(res.resource.Namespace == eResRenderbuffer || res.curType == eGL_RENDERBUFFER)
{
tex.dimension = 2;
tex.type = TextureType::Texture2D;
tex.width = res.width;
tex.height = res.height;
tex.depth = 1;
tex.cubemap = false;
tex.mips = 1;
tex.arraysize = 1;
tex.creationFlags = TextureCategory::ColorTarget;
tex.msQual = 0;
tex.msSamp = RDCMAX(1, res.samples);
if(res.internalFormat == eGL_NONE)
{
tex.format = ResourceFormat();
}
else
{
tex.format = MakeResourceFormat(eGL_TEXTURE_2D, res.internalFormat);
if(IsDepthStencilFormat(res.internalFormat))
tex.creationFlags |= TextureCategory::DepthTarget;
}
tex.byteSize =
(tex.width * tex.height) * (tex.format.compByteWidth * tex.format.compCount) * tex.msSamp;
m_CachedTextures[id] = tex;
return;
}
GLenum target = TextureTarget(res.curType);
GLenum levelQueryType = target;
if(levelQueryType == eGL_TEXTURE_CUBE_MAP)
levelQueryType = eGL_TEXTURE_CUBE_MAP_POSITIVE_X;
GLint width = 1, height = 1, depth = 1, samples = 1;
drv.glGetTextureLevelParameterivEXT(res.resource.name, levelQueryType, 0, eGL_TEXTURE_WIDTH,
&width);
drv.glGetTextureLevelParameterivEXT(res.resource.name, levelQueryType, 0, eGL_TEXTURE_HEIGHT,
&height);
drv.glGetTextureLevelParameterivEXT(res.resource.name, levelQueryType, 0, eGL_TEXTURE_DEPTH,
&depth);
drv.glGetTextureLevelParameterivEXT(res.resource.name, levelQueryType, 0, eGL_TEXTURE_SAMPLES,
&samples);
// the above queries sometimes come back 0, if we have dimensions from creation functions, use
// those
if(width == 0 && res.width > 0)
width = res.width;
if(height == 0 && res.height > 0)
height = res.height;
if(depth == 0 && res.depth > 0)
depth = res.depth;
if(res.width == 0 && width > 0)
{
RDCWARN("TextureData::width didn't get filled out, setting at last minute");
res.width = width;
}
if(res.height == 0 && height > 0)
{
RDCWARN("TextureData::height didn't get filled out, setting at last minute");
res.height = height;
}
if(res.depth == 0 && depth > 0)
{
RDCWARN("TextureData::depth didn't get filled out, setting at last minute");
res.depth = depth;
}
// reasonably common defaults
tex.msQual = 0;
tex.msSamp = 1;
tex.width = tex.height = tex.depth = tex.arraysize = 1;
tex.cubemap = false;
switch(target)
{
case eGL_TEXTURE_BUFFER: tex.type = TextureType::Buffer; break;
case eGL_TEXTURE_1D: tex.type = TextureType::Texture1D; break;
case eGL_TEXTURE_2D: tex.type = TextureType::Texture2D; break;
case eGL_TEXTURE_3D: tex.type = TextureType::Texture3D; break;
case eGL_TEXTURE_1D_ARRAY: tex.type = TextureType::Texture1DArray; break;
case eGL_TEXTURE_2D_ARRAY: tex.type = TextureType::Texture2DArray; break;
case eGL_TEXTURE_RECTANGLE: tex.type = TextureType::TextureRect; break;
case eGL_TEXTURE_2D_MULTISAMPLE: tex.type = TextureType::Texture2DMS; break;
case eGL_TEXTURE_2D_MULTISAMPLE_ARRAY: tex.type = TextureType::Texture2DMSArray; break;
case eGL_TEXTURE_CUBE_MAP: tex.type = TextureType::TextureCube; break;
case eGL_TEXTURE_CUBE_MAP_ARRAY: tex.type = TextureType::TextureCubeArray; break;
default:
tex.type = TextureType::Unknown;
RDCERR("Unexpected texture enum %s", ToStr(target).c_str());
}
switch(target)
{
case eGL_TEXTURE_1D:
case eGL_TEXTURE_BUFFER:
tex.dimension = 1;
tex.width = (uint32_t)width;
break;
case eGL_TEXTURE_1D_ARRAY:
tex.dimension = 1;
tex.width = (uint32_t)width;
tex.arraysize = height;
break;
case eGL_TEXTURE_2D:
case eGL_TEXTURE_RECTANGLE:
case eGL_TEXTURE_2D_MULTISAMPLE:
case eGL_TEXTURE_CUBE_MAP:
tex.dimension = 2;
tex.width = (uint32_t)width;
tex.height = (uint32_t)height;
tex.depth = 1;
tex.arraysize = (target == eGL_TEXTURE_CUBE_MAP ? 6 : 1);
tex.cubemap = (target == eGL_TEXTURE_CUBE_MAP);
tex.msSamp = RDCMAX(1, target == eGL_TEXTURE_2D_MULTISAMPLE ? samples : 1);
break;
case eGL_TEXTURE_2D_ARRAY:
case eGL_TEXTURE_2D_MULTISAMPLE_ARRAY:
case eGL_TEXTURE_CUBE_MAP_ARRAY:
tex.dimension = 2;
tex.width = (uint32_t)width;
tex.height = (uint32_t)height;
tex.depth = 1;
tex.arraysize = depth;
tex.cubemap = (target == eGL_TEXTURE_CUBE_MAP_ARRAY);
tex.msSamp = RDCMAX(1, target == eGL_TEXTURE_2D_MULTISAMPLE_ARRAY ? samples : 1);
break;
case eGL_TEXTURE_3D:
tex.dimension = 3;
tex.width = (uint32_t)width;
tex.height = (uint32_t)height;
tex.depth = (uint32_t)depth;
break;
default: tex.dimension = 2; RDCERR("Unexpected texture enum %s", ToStr(target).c_str());
}
tex.creationFlags = res.creationFlags;
// surely this will be the same for each level... right? that would be insane if it wasn't
GLint fmt = 0;
drv.glGetTextureLevelParameterivEXT(res.resource.name, levelQueryType, 0,
eGL_TEXTURE_INTERNAL_FORMAT, &fmt);
tex.format = MakeResourceFormat(target, (GLenum)fmt);
if(tex.format.compType == CompType::Depth)
tex.creationFlags |= TextureCategory::DepthTarget;
if(target == eGL_TEXTURE_BUFFER)
{
tex.dimension = 1;
tex.height = tex.depth = 1;
tex.cubemap = false;
tex.mips = 1;
tex.arraysize = 1;
tex.creationFlags = TextureCategory::ShaderRead;
tex.msQual = 0;
tex.msSamp = 1;
tex.byteSize = 0;
if(HasExt[ARB_texture_buffer_range])
{
drv.glGetTextureLevelParameterivEXT(res.resource.name, levelQueryType, 0,
eGL_TEXTURE_BUFFER_SIZE, (GLint *)&tex.byteSize);
tex.width = uint32_t(tex.byteSize / RDCMAX(1, tex.format.compByteWidth * tex.format.compCount));
}
m_CachedTextures[id] = tex;
return;
}
if(res.view)
{
tex.mips = Log2Floor(res.mipsValid + 1);
}
else
{
tex.mips = GetNumMips(target, res.resource.name, tex.width, tex.height, tex.depth);
}
GLint compressed = 0;
drv.glGetTextureLevelParameterivEXT(res.resource.name, levelQueryType, 0, eGL_TEXTURE_COMPRESSED,
&compressed);
tex.byteSize = 0;
for(uint32_t m = 0; m < tex.mips; m++)
{
if(fmt == eGL_NONE)
{
}
else if(compressed)
{
tex.byteSize += (uint64_t)GetCompressedByteSize(RDCMAX(1U, tex.width >> m),
RDCMAX(1U, tex.height >> m), 1, (GLenum)fmt);
}
else if(tex.format.Special())
{
tex.byteSize += GetByteSize(RDCMAX(1U, tex.width >> m), RDCMAX(1U, tex.height >> m),
RDCMAX(1U, tex.depth >> m), GetBaseFormat((GLenum)fmt),
GetDataType((GLenum)fmt));
}
else
{
tex.byteSize += RDCMAX(1U, tex.width >> m) * RDCMAX(1U, tex.height >> m) *
RDCMAX(1U, tex.depth >> m) * tex.format.compByteWidth * tex.format.compCount;
}
}
tex.byteSize *= tex.arraysize;
tex.byteSize *= tex.msSamp;
m_CachedTextures[id] = tex;
}
BufferDescription GLReplay::GetBuffer(ResourceId id)
{
BufferDescription ret = {};
MakeCurrentReplayContext(&m_ReplayCtx);
auto &res = m_pDriver->m_Buffers[id];
if(res.resource.Namespace == eResUnknown)
{
RDCERR("Details for invalid buffer id %s requested", ToStr(id).c_str());
RDCEraseEl(ret);
return ret;
}
WrappedOpenGL &drv = *m_pDriver;
ret.resourceId = m_pDriver->GetResourceManager()->GetOriginalID(id);
GLint prevBind = 0;
if(res.curType != eGL_NONE)
{
drv.glGetIntegerv(BufferBinding(res.curType), &prevBind);
drv.glBindBuffer(res.curType, res.resource.name);
}
ret.creationFlags = res.creationFlags;
ret.length = res.size;
if(res.curType != eGL_NONE)
drv.glBindBuffer(res.curType, prevBind);
return ret;
}
TextureDescription GLReplay::GetTexture(ResourceId id)
{
auto it = m_CachedTextures.find(id);
if(it == m_CachedTextures.end())
{
CacheTexture(id);
return m_CachedTextures[id];
}
return it->second;
}
rdcarray<BufferDescription> GLReplay::GetBuffers()
{
rdcarray<BufferDescription> ret;
for(auto it = m_pDriver->m_Buffers.begin(); it != m_pDriver->m_Buffers.end(); ++it)
{
// skip buffers that aren't from the log
if(m_pDriver->GetResourceManager()->GetOriginalID(it->first) == it->first)
continue;
ret.push_back(GetBuffer(it->first));
}
return ret;
}
rdcarray<TextureDescription> GLReplay::GetTextures()
{
rdcarray<TextureDescription> ret;
ret.reserve(m_pDriver->m_Textures.size());
for(auto it = m_pDriver->m_Textures.begin(); it != m_pDriver->m_Textures.end(); ++it)
{
auto &res = m_pDriver->m_Textures[it->first];
// skip textures that aren't from the log (except the 'default backbuffer' textures)
if(!(res.creationFlags & TextureCategory::SwapBuffer) &&
m_pDriver->GetResourceManager()->GetOriginalID(it->first) == it->first)
continue;
CacheTexture(it->first);
ret.push_back(m_CachedTextures[it->first]);
}
return ret;
}
rdcarray<DebugMessage> GLReplay::GetDebugMessages()
{
return m_pDriver->GetDebugMessages();
}
rdcarray<ShaderEntryPoint> GLReplay::GetShaderEntryPoints(ResourceId shader)
{
if(m_pDriver->m_Shaders.find(shader) == m_pDriver->m_Shaders.end())
return {};
WrappedOpenGL::ShaderData &shaderDetails = m_pDriver->m_Shaders[shader];
if(shaderDetails.reflection.resourceId == ResourceId())
{
RDCERR("Can't get shader details without successful reflect");
return {};
}
return {{shaderDetails.reflection.entryPoint, shaderDetails.reflection.stage}};
}
ShaderReflection *GLReplay::GetShader(ResourceId pipeline, ResourceId shader, ShaderEntryPoint entry)
{
auto &shaderDetails = m_pDriver->m_Shaders[shader];
if(shaderDetails.reflection.resourceId == ResourceId())
{
RDCERR("Can't get shader details without successful reflect");
return NULL;
}
return &shaderDetails.reflection;
}
rdcarray<rdcstr> GLReplay::GetDisassemblyTargets(bool withPipeline)
{
return {SPIRVDisassemblyTarget};
}
rdcstr GLReplay::DisassembleShader(ResourceId pipeline, const ShaderReflection *refl,
const rdcstr &target)
{
auto &shaderDetails =
m_pDriver->m_Shaders[m_pDriver->GetResourceManager()->GetLiveID(refl->resourceId)];
if(shaderDetails.sources.empty() && shaderDetails.spirvWords.empty())
return "; Invalid Shader Specified";
if(target == SPIRVDisassemblyTarget || target.empty())
{
rdcstr &disasm = shaderDetails.disassembly;
if(disasm.empty())
disasm = shaderDetails.spirv.Disassemble(refl->entryPoint, shaderDetails.spirvInstructionLines);
return disasm;
}
return StringFormat::Fmt("; Invalid disassembly target %s", target.c_str());
}
void GLReplay::SavePipelineState(uint32_t eventId)
{
GLPipe::State &pipe = m_CurPipelineState;
WrappedOpenGL &drv = *m_pDriver;
GLResourceManager *rm = m_pDriver->GetResourceManager();
MakeCurrentReplayContext(&m_ReplayCtx);
GLRenderState rs;
rs.FetchState(&drv);
// Index buffer
ContextPair &ctx = drv.GetCtx();
GLuint vao = 0;
drv.glGetIntegerv(eGL_VERTEX_ARRAY_BINDING, (GLint *)&vao);
pipe.vertexInput.vertexArrayObject = rm->GetOriginalID(rm->GetResID(VertexArrayRes(ctx, vao)));
GLuint ibuffer = 0;
drv.glGetIntegerv(eGL_ELEMENT_ARRAY_BUFFER_BINDING, (GLint *)&ibuffer);
pipe.vertexInput.indexBuffer = rm->GetOriginalID(rm->GetResID(BufferRes(ctx, ibuffer)));
pipe.vertexInput.primitiveRestart = rs.Enabled[GLRenderState::eEnabled_PrimitiveRestart] ||
rs.Enabled[GLRenderState::eEnabled_PrimitiveRestartFixedIndex];
pipe.vertexInput.restartIndex = rs.Enabled[GLRenderState::eEnabled_PrimitiveRestartFixedIndex]
? ~0U
: rs.PrimitiveRestartIndex;
const GLDrawParams &drawParams = m_pDriver->GetDrawcallParameters(eventId);
pipe.vertexInput.indexByteStride = drawParams.indexWidth;
pipe.vertexInput.topology = drawParams.topo;
// Vertex buffers and attributes
GLint numVBufferBindings = 16;
drv.glGetIntegerv(eGL_MAX_VERTEX_ATTRIB_BINDINGS, &numVBufferBindings);
GLint numVAttribBindings = 16;
drv.glGetIntegerv(eGL_MAX_VERTEX_ATTRIBS, &numVAttribBindings);
pipe.vertexInput.vertexBuffers.resize(numVBufferBindings);
pipe.vertexInput.attributes.resize(numVAttribBindings);
for(GLuint i = 0; i < (GLuint)numVBufferBindings; i++)
{
GLuint buffer = GetBoundVertexBuffer(i);
pipe.vertexInput.vertexBuffers[i].resourceId =
rm->GetOriginalID(rm->GetResID(BufferRes(ctx, buffer)));
drv.glGetIntegeri_v(eGL_VERTEX_BINDING_STRIDE, i,
(GLint *)&pipe.vertexInput.vertexBuffers[i].byteStride);
drv.glGetIntegeri_v(eGL_VERTEX_BINDING_OFFSET, i,
(GLint *)&pipe.vertexInput.vertexBuffers[i].byteOffset);
drv.glGetIntegeri_v(eGL_VERTEX_BINDING_DIVISOR, i,
(GLint *)&pipe.vertexInput.vertexBuffers[i].instanceDivisor);
}
for(GLuint i = 0; i < (GLuint)numVAttribBindings; i++)
{
drv.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_ARRAY_ENABLED,
(GLint *)&pipe.vertexInput.attributes[i].enabled);
drv.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_BINDING,
(GLint *)&pipe.vertexInput.attributes[i].vertexBufferSlot);
drv.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_RELATIVE_OFFSET,
(GLint *)&pipe.vertexInput.attributes[i].byteOffset);
GLenum type = eGL_FLOAT;
GLint normalized = 0;
drv.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_ARRAY_TYPE, (GLint *)&type);
drv.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_ARRAY_NORMALIZED, &normalized);
GLint integer = 0;
drv.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_ARRAY_INTEGER, &integer);
RDCEraseEl(pipe.vertexInput.attributes[i].genericValue);
drv.glGetVertexAttribfv(i, eGL_CURRENT_VERTEX_ATTRIB,
(GLfloat *)pipe.vertexInput.attributes[i].genericValue.floatValue.data());
ResourceFormat fmt;
fmt.type = ResourceFormatType::Regular;
GLint compCount;
drv.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_ARRAY_SIZE, (GLint *)&compCount);
fmt.compCount = (uint8_t)compCount;
switch(type)
{
default:
case eGL_BYTE:
fmt.compByteWidth = 1;
fmt.compType = CompType::SInt;
break;
case eGL_UNSIGNED_BYTE:
fmt.compByteWidth = 1;
fmt.compType = CompType::UInt;
break;
case eGL_SHORT:
fmt.compByteWidth = 2;
fmt.compType = CompType::SInt;
break;
case eGL_UNSIGNED_SHORT:
fmt.compByteWidth = 2;
fmt.compType = CompType::UInt;
break;
case eGL_INT:
fmt.compByteWidth = 4;
fmt.compType = CompType::SInt;
break;
case eGL_UNSIGNED_INT:
fmt.compByteWidth = 4;
fmt.compType = CompType::UInt;
break;
case eGL_FLOAT:
fmt.compByteWidth = 4;
fmt.compType = CompType::Float;
break;
case eGL_DOUBLE:
fmt.compByteWidth = 8;
fmt.compType = CompType::Float;
break;
case eGL_HALF_FLOAT:
fmt.compByteWidth = 2;
fmt.compType = CompType::Float;
break;
case eGL_INT_2_10_10_10_REV:
fmt.type = ResourceFormatType::R10G10B10A2;
fmt.compCount = 4;
fmt.compType = CompType::UInt;
break;
case eGL_UNSIGNED_INT_2_10_10_10_REV:
fmt.type = ResourceFormatType::R10G10B10A2;
fmt.compCount = 4;
fmt.compType = CompType::SInt;
break;
case eGL_UNSIGNED_INT_10F_11F_11F_REV:
fmt.type = ResourceFormatType::R11G11B10;
fmt.compCount = 3;
fmt.compType = CompType::Float;
// spec says this format is never normalized regardless.
normalized = 0;
break;
}
if(compCount == eGL_BGRA)
{
fmt.compByteWidth = 1;
fmt.compCount = 4;
fmt.SetBGRAOrder(true);
fmt.compType = CompType::UNorm;
// spec says BGRA inputs are ALWAYS normalised
normalized = 1;
if(type == eGL_UNSIGNED_INT_2_10_10_10_REV || type == eGL_INT_2_10_10_10_REV)
{
fmt.type = ResourceFormatType::R10G10B10A2;
fmt.compType = type == eGL_UNSIGNED_INT_2_10_10_10_REV ? CompType::UInt : CompType::SInt;
}
else if(type != eGL_UNSIGNED_BYTE)
{
// haven't checked the other cases work properly
RDCERR("Unexpected BGRA type");
}
}
// normalized/floatCast flags are irrelevant for float formats
if(fmt.compType == CompType::SInt || fmt.compType == CompType::UInt)
{
// if it wasn't an integer, it's cast to float
pipe.vertexInput.attributes[i].floatCast = !integer;
// if we're casting, change the component type as appropriate
if(!integer)
{
if(normalized != 0)
fmt.compType = (fmt.compType == CompType::SInt) ? CompType::SNorm : CompType::UNorm;
}
}
else
{
pipe.vertexInput.attributes[i].floatCast = false;
}
pipe.vertexInput.attributes[i].format = fmt;
}
pipe.vertexInput.provokingVertexLast = (rs.ProvokingVertex != eGL_FIRST_VERTEX_CONVENTION);
pipe.vertexProcessing.defaultInnerLevel = rs.PatchParams.defaultInnerLevel;
pipe.vertexProcessing.defaultOuterLevel = rs.PatchParams.defaultOuterLevel;
pipe.vertexProcessing.discard = rs.Enabled[GLRenderState::eEnabled_RasterizerDiscard];
pipe.vertexProcessing.clipOriginLowerLeft = (rs.ClipOrigin != eGL_UPPER_LEFT);
pipe.vertexProcessing.clipNegativeOneToOne = (rs.ClipDepth != eGL_ZERO_TO_ONE);
for(int i = 0; i < 8; i++)
pipe.vertexProcessing.clipPlanes[i] = rs.Enabled[GLRenderState::eEnabled_ClipDistance0 + i];
// Shader stages & Textures
GLint numTexUnits = 8;
drv.glGetIntegerv(eGL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &numTexUnits);
pipe.textures.resize(numTexUnits);
pipe.samplers.resize(numTexUnits);
GLenum activeTexture = eGL_TEXTURE0;
drv.glGetIntegerv(eGL_ACTIVE_TEXTURE, (GLint *)&activeTexture);
pipe.vertexShader.stage = ShaderStage::Vertex;
pipe.tessControlShader.stage = ShaderStage::Tess_Control;
pipe.tessEvalShader.stage = ShaderStage::Tess_Eval;
pipe.geometryShader.stage = ShaderStage::Geometry;
pipe.fragmentShader.stage = ShaderStage::Fragment;
pipe.computeShader.stage = ShaderStage::Compute;
GLuint curProg = 0;
drv.glGetIntegerv(eGL_CURRENT_PROGRAM, (GLint *)&curProg);
GLPipe::Shader *stages[6] = {
&pipe.vertexShader, &pipe.tessControlShader, &pipe.tessEvalShader,
&pipe.geometryShader, &pipe.fragmentShader, &pipe.computeShader,
};
ShaderReflection *refls[6] = {NULL};
ShaderBindpointMapping *mappings[6] = {NULL};
bool spirv[6] = {false};
for(int i = 0; i < 6; i++)
{
stages[i]->programResourceId = stages[i]->shaderResourceId = ResourceId();
stages[i]->reflection = NULL;
stages[i]->bindpointMapping = ShaderBindpointMapping();
}
if(curProg == 0)
{
drv.glGetIntegerv(eGL_PROGRAM_PIPELINE_BINDING, (GLint *)&curProg);
if(curProg == 0)
{
for(GLint unit = 0; unit < numTexUnits; unit++)
{
RDCEraseEl(pipe.textures[unit]);
RDCEraseEl(pipe.samplers[unit]);
}
}
else
{
ResourceId id = rm->GetResID(ProgramPipeRes(ctx, curProg));
auto &pipeDetails = m_pDriver->m_Pipelines[id];
pipe.pipelineResourceId = rm->GetUnreplacedOriginalID(id);
for(size_t i = 0; i < ARRAY_COUNT(pipeDetails.stageShaders); i++)
{
if(pipeDetails.stageShaders[i] != ResourceId())
{
curProg = rm->GetCurrentResource(pipeDetails.stagePrograms[i]).name;
auto &shaderDetails = m_pDriver->m_Shaders[pipeDetails.stageShaders[i]];
if(shaderDetails.reflection.resourceId == ResourceId())
stages[i]->reflection = refls[i] = NULL;
else
stages[i]->reflection = refls[i] = &shaderDetails.reflection;
if(!shaderDetails.spirvWords.empty())
{
stages[i]->bindpointMapping = shaderDetails.mapping;
spirv[i] = true;
EvaluateSPIRVBindpointMapping(curProg, (int)i, refls[i], stages[i]->bindpointMapping);
}
else
{
GetBindpointMapping(curProg, (int)i, refls[i], stages[i]->bindpointMapping);
}
mappings[i] = &stages[i]->bindpointMapping;
stages[i]->programResourceId = rm->GetUnreplacedOriginalID(pipeDetails.stagePrograms[i]);
stages[i]->shaderResourceId = rm->GetUnreplacedOriginalID(pipeDetails.stageShaders[i]);
}
else
{
stages[i]->programResourceId = stages[i]->shaderResourceId = ResourceId();
}
}
}
}
else
{
ResourceId id = rm->GetResID(ProgramRes(ctx, curProg));
auto &progDetails = m_pDriver->m_Programs[id];
pipe.pipelineResourceId = ResourceId();
for(size_t i = 0; i < ARRAY_COUNT(progDetails.stageShaders); i++)
{
if(progDetails.stageShaders[i] != ResourceId())
{
auto &shaderDetails = m_pDriver->m_Shaders[progDetails.stageShaders[i]];
if(shaderDetails.reflection.resourceId == ResourceId())
stages[i]->reflection = refls[i] = NULL;
else
stages[i]->reflection = refls[i] = &shaderDetails.reflection;
if(!shaderDetails.spirvWords.empty())
{
stages[i]->bindpointMapping = shaderDetails.mapping;
spirv[i] = true;
EvaluateSPIRVBindpointMapping(curProg, (int)i, refls[i], stages[i]->bindpointMapping);
}
else
{
GetBindpointMapping(curProg, (int)i, refls[i], stages[i]->bindpointMapping);
}
mappings[i] = &stages[i]->bindpointMapping;
stages[i]->programResourceId = rm->GetUnreplacedOriginalID(id);
stages[i]->shaderResourceId = rm->GetUnreplacedOriginalID(progDetails.stageShaders[i]);
}
else
{
stages[i]->programResourceId = stages[i]->shaderResourceId = ResourceId();
}
}
}
// !!!NOTE!!! This function will MODIFY the refls[] binding arrays.
// See inside this function for what it does and why.
for(size_t i = 0; i < ARRAY_COUNT(refls); i++)
{
// don't resort if it's SPIR-V
if(spirv[i])
continue;
ResortBindings(refls[i], mappings[i]);
}
RDCEraseEl(pipe.transformFeedback);
if(HasExt[ARB_transform_feedback2])
{
GLuint feedback = 0;
drv.glGetIntegerv(eGL_TRANSFORM_FEEDBACK_BINDING, (GLint *)&feedback);
if(feedback != 0)
pipe.transformFeedback.feedbackResourceId =
rm->GetOriginalID(rm->GetResID(FeedbackRes(ctx, feedback)));
else
pipe.transformFeedback.feedbackResourceId = ResourceId();
GLint maxCount = 0;
drv.glGetIntegerv(eGL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS, &maxCount);
for(int i = 0; i < (int)ARRAY_COUNT(pipe.transformFeedback.bufferResourceId) && i < maxCount; i++)
{
GLuint buffer = 0;
drv.glGetIntegeri_v(eGL_TRANSFORM_FEEDBACK_BUFFER_BINDING, i, (GLint *)&buffer);
pipe.transformFeedback.bufferResourceId[i] =
rm->GetOriginalID(rm->GetResID(BufferRes(ctx, buffer)));
drv.glGetInteger64i_v(eGL_TRANSFORM_FEEDBACK_BUFFER_START, i,
(GLint64 *)&pipe.transformFeedback.byteOffset[i]);
drv.glGetInteger64i_v(eGL_TRANSFORM_FEEDBACK_BUFFER_SIZE, i,
(GLint64 *)&pipe.transformFeedback.byteSize[i]);
}
GLint p = 0;
drv.glGetIntegerv(eGL_TRANSFORM_FEEDBACK_BUFFER_PAUSED, &p);
pipe.transformFeedback.paused = (p != 0);
drv.glGetIntegerv(eGL_TRANSFORM_FEEDBACK_BUFFER_ACTIVE, &p);
pipe.transformFeedback.active = (p != 0) || m_pDriver->m_WasActiveFeedback;
}
for(int i = 0; i < 6; i++)
{
size_t num = RDCMIN(128, rs.Subroutines[i].numSubroutines);
if(num == 0)
{
RDCEraseEl(stages[i]->subroutines);
}
else
{
stages[i]->subroutines.resize(num);
memcpy(stages[i]->subroutines.data(), rs.Subroutines[i].Values, num * sizeof(uint32_t));
}
}
// GL is ass-backwards in its handling of texture units. When a shader is active
// the types in the glsl samplers inform which targets are used from which texture units
//
// So texture unit 5 can have a 2D bound (texture 52) and a Cube bound (texture 77).
// * if a uniform sampler2D has value 5 then the 2D texture is used, and we sample from 52
// * if a uniform samplerCube has value 5 then the Cube texture is used, and we sample from 77
// It's illegal for both a sampler2D and samplerCube to both have the same value (or any two
// different types). It makes it all rather pointless and needlessly complex.
//
// What we have to do then, is consider the program, look at the values of the uniforms, and
// then get the appropriate current binding based on the uniform type. We can warn/alert the
// user if we hit the illegal case of two uniforms with different types but the same value
//
// Handling is different if no shaders are active, but we don't consider that case.
for(GLint unit = 0; unit < numTexUnits; unit++)
{
GLenum binding = eGL_NONE;
GLenum target = eGL_NONE;
TextureType resType = TextureType::Unknown;
for(size_t s = 0; s < ARRAY_COUNT(refls); s++)
{
if(refls[s] == NULL)
continue;
for(const ShaderResource &res : refls[s]->readOnlyResources)
{
// bindPoint is the uniform value for this sampler
if(mappings[s]->readOnlyResources[res.bindPoint].bind == unit)
{
GLenum t = eGL_NONE;
switch(res.resType)
{
case TextureType::Unknown: target = eGL_NONE; break;
case TextureType::Buffer: target = eGL_TEXTURE_BUFFER; break;
case TextureType::Texture1D: target = eGL_TEXTURE_1D; break;
case TextureType::Texture1DArray: target = eGL_TEXTURE_1D_ARRAY; break;
case TextureType::Texture2D: target = eGL_TEXTURE_2D; break;
case TextureType::TextureRect: target = eGL_TEXTURE_RECTANGLE; break;
case TextureType::Texture2DArray: target = eGL_TEXTURE_2D_ARRAY; break;
case TextureType::Texture2DMS: target = eGL_TEXTURE_2D_MULTISAMPLE; break;
case TextureType::Texture2DMSArray: target = eGL_TEXTURE_2D_MULTISAMPLE_ARRAY; break;
case TextureType::Texture3D: target = eGL_TEXTURE_3D; break;
case TextureType::TextureCube: target = eGL_TEXTURE_CUBE_MAP; break;
case TextureType::TextureCubeArray: target = eGL_TEXTURE_CUBE_MAP_ARRAY; break;
case TextureType::Count: RDCERR("Invalid shader resource type"); break;
}
if(target != eGL_NONE)
t = TextureBinding(target);
resType = res.resType;
if(binding == eGL_NONE)
{
binding = t;
}
else if(binding == t)
{
// two uniforms with the same type pointing to the same slot is fine
binding = t;
}
else if(binding != t)
{
RDCWARN("Two uniforms pointing to texture unit %d with types %s and %s", unit,
ToStr(binding).c_str(), ToStr(t).c_str());
}
}
}
}
if(binding != eGL_NONE)
{
drv.glActiveTexture(GLenum(eGL_TEXTURE0 + unit));
GLuint tex = 0;
if(binding == eGL_TEXTURE_CUBE_MAP_ARRAY && !HasExt[ARB_texture_cube_map_array])
tex = 0;
else
drv.glGetIntegerv(binding, (GLint *)&tex);
if(tex == 0)
{
pipe.textures[unit].resourceId = ResourceId();
pipe.textures[unit].firstMip = 0;
pipe.textures[unit].numMips = 1;
pipe.textures[unit].type = TextureType::Unknown;
pipe.textures[unit].depthReadChannel = -1;
pipe.textures[unit].swizzle.red = TextureSwizzle::Red;
pipe.textures[unit].swizzle.green = TextureSwizzle::Green;
pipe.textures[unit].swizzle.blue = TextureSwizzle::Blue;
pipe.textures[unit].swizzle.alpha = TextureSwizzle::Alpha;
RDCEraseEl(pipe.samplers[unit].borderColor);
pipe.samplers[unit].addressS = AddressMode::Wrap;
pipe.samplers[unit].addressT = AddressMode::Wrap;
pipe.samplers[unit].addressR = AddressMode::Wrap;
pipe.samplers[unit].compareFunction = CompareFunction::AlwaysTrue;
pipe.samplers[unit].filter = TextureFilter();
pipe.samplers[unit].seamlessCubeMap = false;
pipe.samplers[unit].maxAnisotropy = 0.0f;
pipe.samplers[unit].maxLOD = 0.0f;
pipe.samplers[unit].minLOD = 0.0f;
pipe.samplers[unit].mipLODBias = 0.0f;
}
else
{
GLint firstMip = 0, numMips = 1;
if(target != eGL_TEXTURE_BUFFER)
{
drv.glGetTextureParameterivEXT(tex, target, eGL_TEXTURE_BASE_LEVEL, &firstMip);
drv.glGetTextureParameterivEXT(tex, target, eGL_TEXTURE_MAX_LEVEL, &numMips);
numMips = numMips - firstMip + 1;
}
pipe.textures[unit].resourceId = rm->GetOriginalID(rm->GetResID(TextureRes(ctx, tex)));
pipe.textures[unit].firstMip = (uint32_t)firstMip;
pipe.textures[unit].numMips = (uint32_t)numMips;
pipe.textures[unit].type = resType;
pipe.textures[unit].depthReadChannel = -1;
GLenum levelQueryType =
target == eGL_TEXTURE_CUBE_MAP ? eGL_TEXTURE_CUBE_MAP_POSITIVE_X : target;
GLenum fmt = eGL_NONE;
drv.glGetTexLevelParameteriv(levelQueryType, 0, eGL_TEXTURE_INTERNAL_FORMAT, (GLint *)&fmt);
if(IsDepthStencilFormat(fmt))
{
GLint depthMode = eGL_DEPTH_COMPONENT;
if(HasExt[ARB_stencil_texturing])
drv.glGetTextureParameterivEXT(tex, target, eGL_DEPTH_STENCIL_TEXTURE_MODE, &depthMode);
if(depthMode == eGL_DEPTH_COMPONENT)
pipe.textures[unit].depthReadChannel = 0;
else if(depthMode == eGL_STENCIL_INDEX)
pipe.textures[unit].depthReadChannel = 1;
}
GLenum swizzles[4] = {eGL_RED, eGL_GREEN, eGL_BLUE, eGL_ALPHA};
if(target != eGL_TEXTURE_BUFFER &&
(HasExt[ARB_texture_swizzle] || HasExt[EXT_texture_swizzle]))
GetTextureSwizzle(tex, target, swizzles);
pipe.textures[unit].swizzle.red = MakeSwizzle(swizzles[0]);
pipe.textures[unit].swizzle.green = MakeSwizzle(swizzles[1]);
pipe.textures[unit].swizzle.blue = MakeSwizzle(swizzles[2]);
pipe.textures[unit].swizzle.alpha = MakeSwizzle(swizzles[3]);
GLuint samp = 0;
if(HasExt[ARB_sampler_objects])
drv.glGetIntegerv(eGL_SAMPLER_BINDING, (GLint *)&samp);
pipe.samplers[unit].resourceId = rm->GetOriginalID(rm->GetResID(SamplerRes(ctx, samp)));
// checking texture completeness is a pretty expensive operation since it requires a lot of
// queries against the driver's texture properties.
// We assume that if a texture and sampler are complete at any point, even if their
// properties change mid-frame they will stay complete. Similarly if they are _incomplete_
// they will stay incomplete. Thus we can cache the results for a given pair, which if
// samplers don't change (or are only ever used consistently with the same texture) amounts
// to one entry per texture.
// Note that textures can't change target, so we don't need to icnlude the target in the key
CompleteCacheKey complete = {tex, samp};
auto it = m_CompleteCache.find(complete);
if(it == m_CompleteCache.end())
it = m_CompleteCache.insert(
it, std::make_pair(complete, GetTextureCompleteStatus(target, tex, samp)));
pipe.textures[unit].completeStatus = it->second;
if(target != eGL_TEXTURE_BUFFER && target != eGL_TEXTURE_2D_MULTISAMPLE &&
target != eGL_TEXTURE_2D_MULTISAMPLE_ARRAY)
{
if(samp != 0)
drv.glGetSamplerParameterfv(samp, eGL_TEXTURE_BORDER_COLOR,
pipe.samplers[unit].borderColor.data());
else
drv.glGetTextureParameterfvEXT(tex, target, eGL_TEXTURE_BORDER_COLOR,
pipe.samplers[unit].borderColor.data());
GLint v;
v = 0;
if(samp != 0)
drv.glGetSamplerParameteriv(samp, eGL_TEXTURE_WRAP_S, &v);
else
drv.glGetTextureParameterivEXT(tex, target, eGL_TEXTURE_WRAP_S, &v);
pipe.samplers[unit].addressS = MakeAddressMode((GLenum)v);
v = 0;
if(samp != 0)
drv.glGetSamplerParameteriv(samp, eGL_TEXTURE_WRAP_T, &v);
else
drv.glGetTextureParameterivEXT(tex, target, eGL_TEXTURE_WRAP_T, &v);
pipe.samplers[unit].addressT = MakeAddressMode((GLenum)v);
v = 0;
if(samp != 0)
drv.glGetSamplerParameteriv(samp, eGL_TEXTURE_WRAP_R, &v);
else
drv.glGetTextureParameterivEXT(tex, target, eGL_TEXTURE_WRAP_R, &v);
pipe.samplers[unit].addressR = MakeAddressMode((GLenum)v);
v = 0;
if(HasExt[ARB_seamless_cubemap_per_texture])
{
if(samp != 0)
drv.glGetSamplerParameteriv(samp, eGL_TEXTURE_CUBE_MAP_SEAMLESS, &v);
else
drv.glGetTextureParameterivEXT(tex, target, eGL_TEXTURE_CUBE_MAP_SEAMLESS, &v);
}
pipe.samplers[unit].seamlessCubeMap =
(v != 0 || rs.Enabled[GLRenderState::eEnabled_TexCubeSeamless]);
v = 0;
if(samp != 0)
drv.glGetSamplerParameteriv(samp, eGL_TEXTURE_COMPARE_FUNC, &v);
else
drv.glGetTextureParameterivEXT(tex, target, eGL_TEXTURE_COMPARE_FUNC, &v);
pipe.samplers[unit].compareFunction = MakeCompareFunc((GLenum)v);
GLint minf = 0;
GLint magf = 0;
if(samp != 0)
drv.glGetSamplerParameteriv(samp, eGL_TEXTURE_MIN_FILTER, &minf);
else
drv.glGetTextureParameterivEXT(tex, target, eGL_TEXTURE_MIN_FILTER, &minf);
if(samp != 0)
drv.glGetSamplerParameteriv(samp, eGL_TEXTURE_MAG_FILTER, &magf);
else
drv.glGetTextureParameterivEXT(tex, target, eGL_TEXTURE_MAG_FILTER, &magf);
if(HasExt[ARB_texture_filter_anisotropic])
{
if(samp != 0)
drv.glGetSamplerParameterfv(samp, eGL_TEXTURE_MAX_ANISOTROPY,
&pipe.samplers[unit].maxAnisotropy);
else
drv.glGetTextureParameterfvEXT(tex, target, eGL_TEXTURE_MAX_ANISOTROPY,
&pipe.samplers[unit].maxAnisotropy);
}
else
{
pipe.samplers[unit].maxAnisotropy = 0.0f;
}
pipe.samplers[unit].filter =
MakeFilter((GLenum)minf, (GLenum)magf, pipe.samplers[unit].maxAnisotropy);
v = 0;
if(samp != 0)
drv.glGetSamplerParameteriv(samp, eGL_TEXTURE_COMPARE_MODE, &v);
else
drv.glGetTextureParameterivEXT(tex, target, eGL_TEXTURE_COMPARE_MODE, &v);
pipe.samplers[unit].filter.filter = (GLenum)v == eGL_COMPARE_REF_TO_TEXTURE
? FilterFunction::Comparison
: FilterFunction::Normal;
if(samp != 0)
drv.glGetSamplerParameterfv(samp, eGL_TEXTURE_MAX_LOD, &pipe.samplers[unit].maxLOD);
else
drv.glGetTextureParameterfvEXT(tex, target, eGL_TEXTURE_MAX_LOD,
&pipe.samplers[unit].maxLOD);
if(samp != 0)
drv.glGetSamplerParameterfv(samp, eGL_TEXTURE_MIN_LOD, &pipe.samplers[unit].minLOD);
else
drv.glGetTextureParameterfvEXT(tex, target, eGL_TEXTURE_MIN_LOD,
&pipe.samplers[unit].minLOD);
if(!IsGLES)
{
if(samp != 0)
drv.glGetSamplerParameterfv(samp, eGL_TEXTURE_LOD_BIAS,
&pipe.samplers[unit].mipLODBias);
else
drv.glGetTextureParameterfvEXT(tex, target, eGL_TEXTURE_LOD_BIAS,
&pipe.samplers[unit].mipLODBias);
}
else
{
pipe.samplers[unit].mipLODBias = 0.0f;
}
}
else
{
// texture buffers don't support sampling
RDCEraseEl(pipe.samplers[unit].borderColor);
pipe.samplers[unit].addressS = AddressMode::Wrap;
pipe.samplers[unit].addressT = AddressMode::Wrap;
pipe.samplers[unit].addressR = AddressMode::Wrap;
pipe.samplers[unit].compareFunction = CompareFunction::AlwaysTrue;
pipe.samplers[unit].filter = TextureFilter();
pipe.samplers[unit].seamlessCubeMap = false;
pipe.samplers[unit].maxAnisotropy = 0.0f;
pipe.samplers[unit].maxLOD = 0.0f;
pipe.samplers[unit].minLOD = 0.0f;
pipe.samplers[unit].mipLODBias = 0.0f;
}
}
}
else
{
// what should we do in this case? there could be something bound just not used,
// it'd be nice to return that
}
}
drv.glActiveTexture(activeTexture);
pipe.uniformBuffers.resize(ARRAY_COUNT(rs.UniformBinding));
for(size_t b = 0; b < pipe.uniformBuffers.size(); b++)
{
if(rs.UniformBinding[b].res.name == 0)
{
pipe.uniformBuffers[b].resourceId = ResourceId();
pipe.uniformBuffers[b].byteOffset = pipe.uniformBuffers[b].byteSize = 0;
}
else
{
pipe.uniformBuffers[b].resourceId = rm->GetOriginalID(rm->GetResID(rs.UniformBinding[b].res));
pipe.uniformBuffers[b].byteOffset = rs.UniformBinding[b].start;
pipe.uniformBuffers[b].byteSize = rs.UniformBinding[b].size;
}
}
pipe.atomicBuffers.resize(ARRAY_COUNT(rs.AtomicCounter));
for(size_t b = 0; b < pipe.atomicBuffers.size(); b++)
{
if(rs.AtomicCounter[b].res.name == 0)
{
pipe.atomicBuffers[b].resourceId = ResourceId();
pipe.atomicBuffers[b].byteOffset = pipe.atomicBuffers[b].byteSize = 0;
}
else
{
pipe.atomicBuffers[b].resourceId = rm->GetOriginalID(rm->GetResID(rs.AtomicCounter[b].res));
pipe.atomicBuffers[b].byteOffset = rs.AtomicCounter[b].start;
pipe.atomicBuffers[b].byteSize = rs.AtomicCounter[b].size;
}
}
pipe.shaderStorageBuffers.resize(ARRAY_COUNT(rs.ShaderStorage));
for(size_t b = 0; b < pipe.shaderStorageBuffers.size(); b++)
{
if(rs.ShaderStorage[b].res.name == 0)
{
pipe.shaderStorageBuffers[b].resourceId = ResourceId();
pipe.shaderStorageBuffers[b].byteOffset = pipe.shaderStorageBuffers[b].byteSize = 0;
}
else
{
pipe.shaderStorageBuffers[b].resourceId =
rm->GetOriginalID(rm->GetResID(rs.ShaderStorage[b].res));
pipe.shaderStorageBuffers[b].byteOffset = rs.ShaderStorage[b].start;
pipe.shaderStorageBuffers[b].byteSize = rs.ShaderStorage[b].size;
}
}
pipe.images.resize(ARRAY_COUNT(rs.Images));
for(size_t i = 0; i < pipe.images.size(); i++)
{
if(rs.Images[i].res.name == 0)
{
RDCEraseEl(pipe.images[i]);
}
else
{
ResourceId id = rm->GetResID(rs.Images[i].res);
pipe.images[i].resourceId = rm->GetOriginalID(id);
pipe.images[i].mipLevel = rs.Images[i].level;
pipe.images[i].layered = rs.Images[i].layered;
pipe.images[i].slice = rs.Images[i].layer;
if(rs.Images[i].access == eGL_READ_ONLY)
{
pipe.images[i].readAllowed = true;
pipe.images[i].writeAllowed = false;
}
else if(rs.Images[i].access == eGL_WRITE_ONLY)
{
pipe.images[i].readAllowed = false;
pipe.images[i].writeAllowed = true;
}
else
{
pipe.images[i].readAllowed = true;
pipe.images[i].writeAllowed = true;
}
pipe.images[i].imageFormat = MakeResourceFormat(eGL_TEXTURE_2D, rs.Images[i].format);
CacheTexture(id);
pipe.images[i].type = m_CachedTextures[id].type;
}
}
// Vertex post processing and rasterization
RDCCOMPILE_ASSERT(ARRAY_COUNT(rs.Viewports) == ARRAY_COUNT(rs.DepthRanges),
"GL Viewport count does not match depth ranges count");
pipe.rasterizer.viewports.resize(ARRAY_COUNT(rs.Viewports));
for(size_t v = 0; v < pipe.rasterizer.viewports.size(); ++v)
{
pipe.rasterizer.viewports[v].x = rs.Viewports[v].x;
pipe.rasterizer.viewports[v].y = rs.Viewports[v].y;
pipe.rasterizer.viewports[v].width = rs.Viewports[v].width;
pipe.rasterizer.viewports[v].height = rs.Viewports[v].height;
pipe.rasterizer.viewports[v].minDepth = (float)rs.DepthRanges[v].nearZ;
pipe.rasterizer.viewports[v].maxDepth = (float)rs.DepthRanges[v].farZ;
}
pipe.rasterizer.scissors.resize(ARRAY_COUNT(rs.Scissors));
for(size_t s = 0; s < pipe.rasterizer.scissors.size(); ++s)
{
pipe.rasterizer.scissors[s].x = rs.Scissors[s].x;
pipe.rasterizer.scissors[s].y = rs.Scissors[s].y;
pipe.rasterizer.scissors[s].width = rs.Scissors[s].width;
pipe.rasterizer.scissors[s].height = rs.Scissors[s].height;
pipe.rasterizer.scissors[s].enabled = rs.Scissors[s].enabled;
}
int polygonOffsetEnableEnum;
switch(rs.PolygonMode)
{
default:
RDCWARN("Unexpected value for POLYGON_MODE %x", rs.PolygonMode);
DELIBERATE_FALLTHROUGH();
case eGL_FILL:
pipe.rasterizer.state.fillMode = FillMode::Solid;
polygonOffsetEnableEnum = GLRenderState::eEnabled_PolyOffsetFill;
break;
case eGL_LINE:
pipe.rasterizer.state.fillMode = FillMode::Wireframe;
polygonOffsetEnableEnum = GLRenderState::eEnabled_PolyOffsetLine;
break;
case eGL_POINT:
pipe.rasterizer.state.fillMode = FillMode::Point;
polygonOffsetEnableEnum = GLRenderState::eEnabled_PolyOffsetPoint;
break;
}
if(rs.Enabled[polygonOffsetEnableEnum])
{
pipe.rasterizer.state.depthBias = rs.PolygonOffset[1];
pipe.rasterizer.state.slopeScaledDepthBias = rs.PolygonOffset[0];
pipe.rasterizer.state.offsetClamp = rs.PolygonOffset[2];
}
else
{
pipe.rasterizer.state.depthBias = 0.0f;
pipe.rasterizer.state.slopeScaledDepthBias = 0.0f;
pipe.rasterizer.state.offsetClamp = 0.0f;
}
if(rs.Enabled[GLRenderState::eEnabled_CullFace])
{
switch(rs.CullFace)
{
default: RDCWARN("Unexpected value for CULL_FACE %x", rs.CullFace); DELIBERATE_FALLTHROUGH();
case eGL_BACK: pipe.rasterizer.state.cullMode = CullMode::Back; break;
case eGL_FRONT: pipe.rasterizer.state.cullMode = CullMode::Front; break;
case eGL_FRONT_AND_BACK: pipe.rasterizer.state.cullMode = CullMode::FrontAndBack; break;
}
}
else
{
pipe.rasterizer.state.cullMode = CullMode::NoCull;
}
RDCASSERT(rs.FrontFace == eGL_CCW || rs.FrontFace == eGL_CW);
pipe.rasterizer.state.frontCCW = rs.FrontFace == eGL_CCW;
pipe.rasterizer.state.depthClamp = rs.Enabled[GLRenderState::eEnabled_DepthClamp];
pipe.rasterizer.state.multisampleEnable = rs.Enabled[GLRenderState::eEnabled_Multisample];
pipe.rasterizer.state.sampleShading = rs.Enabled[GLRenderState::eEnabled_SampleShading];
pipe.rasterizer.state.sampleMask = rs.Enabled[GLRenderState::eEnabled_SampleMask];
pipe.rasterizer.state.sampleMaskValue =
rs.SampleMask[0]; // assume number of samples is less than 32
pipe.rasterizer.state.sampleCoverage = rs.Enabled[GLRenderState::eEnabled_SampleCoverage];
pipe.rasterizer.state.sampleCoverageInvert = rs.SampleCoverageInvert;
pipe.rasterizer.state.sampleCoverageValue = rs.SampleCoverage;
pipe.rasterizer.state.alphaToCoverage = rs.Enabled[GLRenderState::eEnabled_SampleAlphaToCoverage];
pipe.rasterizer.state.alphaToOne = rs.Enabled[GLRenderState::eEnabled_SampleAlphaToOne];
pipe.rasterizer.state.minSampleShadingRate = rs.MinSampleShading;
pipe.rasterizer.state.programmablePointSize = rs.Enabled[rs.eEnabled_ProgramPointSize];
pipe.rasterizer.state.pointSize = rs.PointSize;
pipe.rasterizer.state.lineWidth = rs.LineWidth;
pipe.rasterizer.state.pointFadeThreshold = rs.PointFadeThresholdSize;
pipe.rasterizer.state.pointOriginUpperLeft = (rs.PointSpriteOrigin != eGL_LOWER_LEFT);
// depth and stencil states
pipe.depthState.depthEnable = rs.Enabled[GLRenderState::eEnabled_DepthTest];
pipe.depthState.depthWrites = rs.DepthWriteMask != 0;
pipe.depthState.depthFunction = MakeCompareFunc(rs.DepthFunc);
pipe.depthState.depthBounds = rs.Enabled[GLRenderState::eEnabled_DepthBoundsEXT];
pipe.depthState.nearBound = rs.DepthBounds.nearZ;
pipe.depthState.farBound = rs.DepthBounds.farZ;
pipe.stencilState.stencilEnable = rs.Enabled[GLRenderState::eEnabled_StencilTest];
pipe.stencilState.frontFace.compareMask = rs.StencilFront.valuemask;
pipe.stencilState.frontFace.writeMask = rs.StencilFront.writemask;
pipe.stencilState.frontFace.reference = uint8_t(rs.StencilFront.ref & 0xff);
pipe.stencilState.frontFace.function = MakeCompareFunc(rs.StencilFront.func);
pipe.stencilState.frontFace.passOperation = MakeStencilOp(rs.StencilFront.pass);
pipe.stencilState.frontFace.failOperation = MakeStencilOp(rs.StencilFront.stencilFail);
pipe.stencilState.frontFace.depthFailOperation = MakeStencilOp(rs.StencilFront.depthFail);
pipe.stencilState.backFace.compareMask = rs.StencilBack.valuemask;
pipe.stencilState.backFace.writeMask = rs.StencilBack.writemask;
pipe.stencilState.backFace.reference = uint8_t(rs.StencilBack.ref & 0xff);
pipe.stencilState.backFace.function = MakeCompareFunc(rs.StencilBack.func);
pipe.stencilState.backFace.passOperation = MakeStencilOp(rs.StencilBack.pass);
pipe.stencilState.backFace.failOperation = MakeStencilOp(rs.StencilBack.stencilFail);
pipe.stencilState.backFace.depthFailOperation = MakeStencilOp(rs.StencilBack.depthFail);
// Frame buffer
GLuint curDrawFBO = 0;
drv.glGetIntegerv(eGL_DRAW_FRAMEBUFFER_BINDING, (GLint *)&curDrawFBO);
GLuint curReadFBO = 0;
drv.glGetIntegerv(eGL_READ_FRAMEBUFFER_BINDING, (GLint *)&curReadFBO);
GLint numCols = 8;
drv.glGetIntegerv(eGL_MAX_COLOR_ATTACHMENTS, &numCols);
bool rbCol[32] = {false};
bool rbDepth = false;
bool rbStencil = false;
GLuint curCol[32] = {0};
GLuint curDepth = 0;
GLuint curStencil = 0;
RDCASSERT(numCols <= 32);
// we should never bind the true default framebuffer - if the app did, we will have our fake bound
RDCASSERT(curDrawFBO != 0);
RDCASSERT(curReadFBO != 0);
{
GLenum type = eGL_TEXTURE;
for(GLint i = 0; i < numCols; i++)
{
drv.glGetFramebufferAttachmentParameteriv(
eGL_DRAW_FRAMEBUFFER, GLenum(eGL_COLOR_ATTACHMENT0 + i),
eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, (GLint *)&curCol[i]);
drv.glGetFramebufferAttachmentParameteriv(
eGL_DRAW_FRAMEBUFFER, GLenum(eGL_COLOR_ATTACHMENT0 + i),
eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, (GLint *)&type);
if(type == eGL_RENDERBUFFER)
rbCol[i] = true;
}
drv.glGetFramebufferAttachmentParameteriv(eGL_DRAW_FRAMEBUFFER, eGL_DEPTH_ATTACHMENT,
eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME,
(GLint *)&curDepth);
drv.glGetFramebufferAttachmentParameteriv(eGL_DRAW_FRAMEBUFFER, eGL_DEPTH_ATTACHMENT,
eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, (GLint *)&type);
if(type == eGL_RENDERBUFFER)
rbDepth = true;
drv.glGetFramebufferAttachmentParameteriv(eGL_DRAW_FRAMEBUFFER, eGL_STENCIL_ATTACHMENT,
eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME,
(GLint *)&curStencil);
drv.glGetFramebufferAttachmentParameteriv(eGL_DRAW_FRAMEBUFFER, eGL_STENCIL_ATTACHMENT,
eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, (GLint *)&type);
if(type == eGL_RENDERBUFFER)
rbStencil = true;
pipe.framebuffer.drawFBO.resourceId =
rm->GetOriginalID(rm->GetResID(FramebufferRes(ctx, curDrawFBO)));
pipe.framebuffer.drawFBO.colorAttachments.resize(numCols);
for(GLint i = 0; i < numCols; i++)
{
ResourceId id =
rm->GetResID(rbCol[i] ? RenderbufferRes(ctx, curCol[i]) : TextureRes(ctx, curCol[i]));
pipe.framebuffer.drawFBO.colorAttachments[i].resourceId = rm->GetOriginalID(id);
GLenum attachment = GLenum(eGL_COLOR_ATTACHMENT0 + i);
if(pipe.framebuffer.drawFBO.colorAttachments[i].resourceId != ResourceId() && !rbCol[i])
GetFramebufferMipAndLayer(curDrawFBO, attachment,
(GLint *)&pipe.framebuffer.drawFBO.colorAttachments[i].mipLevel,
(GLint *)&pipe.framebuffer.drawFBO.colorAttachments[i].slice);
pipe.framebuffer.drawFBO.colorAttachments[i].numSlices = 1;
if(!rbCol[i] && id != ResourceId())
{
// desktop GL allows layered attachments which attach all slices from 0 to N
if(!IsGLES)
{
GLint layered = 0;
GL.glGetNamedFramebufferAttachmentParameterivEXT(
curDrawFBO, attachment, eGL_FRAMEBUFFER_ATTACHMENT_LAYERED, &layered);
if(layered)
{
pipe.framebuffer.drawFBO.colorAttachments[i].numSlices = m_pDriver->m_Textures[id].depth;
}
}
else
{
// on GLES there's an OVR extension that allows attaching multiple layers
if(HasExt[OVR_multiview])
{
GLint numViews = 0, startView = 0;
GL.glGetNamedFramebufferAttachmentParameterivEXT(
curDrawFBO, attachment, eGL_FRAMEBUFFER_ATTACHMENT_TEXTURE_NUM_VIEWS_OVR, &numViews);
GL.glGetNamedFramebufferAttachmentParameterivEXT(
curDrawFBO, attachment, eGL_FRAMEBUFFER_ATTACHMENT_TEXTURE_BASE_VIEW_INDEX_OVR,
&startView);
if(numViews > 1)
{
pipe.framebuffer.drawFBO.colorAttachments[i].numSlices = numViews;
pipe.framebuffer.drawFBO.colorAttachments[i].slice = startView;
}
}
}
}
GLenum swizzles[4] = {eGL_RED, eGL_GREEN, eGL_BLUE, eGL_ALPHA};
if(!rbCol[i] && id != ResourceId() &&
(HasExt[ARB_texture_swizzle] || HasExt[EXT_texture_swizzle]))
{
GLenum target = m_pDriver->m_Textures[id].curType;
GetTextureSwizzle(curCol[i], target, swizzles);
}
pipe.framebuffer.drawFBO.colorAttachments[i].swizzle.red = MakeSwizzle(swizzles[0]);
pipe.framebuffer.drawFBO.colorAttachments[i].swizzle.green = MakeSwizzle(swizzles[1]);
pipe.framebuffer.drawFBO.colorAttachments[i].swizzle.blue = MakeSwizzle(swizzles[2]);
pipe.framebuffer.drawFBO.colorAttachments[i].swizzle.alpha = MakeSwizzle(swizzles[3]);
}
pipe.framebuffer.drawFBO.depthAttachment.resourceId = rm->GetOriginalID(
rm->GetResID(rbDepth ? RenderbufferRes(ctx, curDepth) : TextureRes(ctx, curDepth)));
pipe.framebuffer.drawFBO.stencilAttachment.resourceId = rm->GetOriginalID(
rm->GetResID(rbStencil ? RenderbufferRes(ctx, curStencil) : TextureRes(ctx, curStencil)));
if(pipe.framebuffer.drawFBO.depthAttachment.resourceId != ResourceId() && !rbDepth)
GetFramebufferMipAndLayer(curDrawFBO, eGL_DEPTH_ATTACHMENT,
(GLint *)&pipe.framebuffer.drawFBO.depthAttachment.mipLevel,
(GLint *)&pipe.framebuffer.drawFBO.depthAttachment.slice);
if(pipe.framebuffer.drawFBO.stencilAttachment.resourceId != ResourceId() && !rbStencil)
GetFramebufferMipAndLayer(curDrawFBO, eGL_STENCIL_ATTACHMENT,
(GLint *)&pipe.framebuffer.drawFBO.stencilAttachment.mipLevel,
(GLint *)&pipe.framebuffer.drawFBO.stencilAttachment.slice);
pipe.framebuffer.drawFBO.depthAttachment.numSlices = 1;
pipe.framebuffer.drawFBO.stencilAttachment.numSlices = 1;
ResourceId id = pipe.framebuffer.drawFBO.depthAttachment.resourceId;
if(!rbDepth && id != ResourceId())
{
// desktop GL allows layered attachments which attach all slices from 0 to N
if(!IsGLES)
{
GLint layered = 0;
GL.glGetNamedFramebufferAttachmentParameterivEXT(
curDrawFBO, eGL_DEPTH_ATTACHMENT, eGL_FRAMEBUFFER_ATTACHMENT_LAYERED, &layered);
if(layered)
{
pipe.framebuffer.drawFBO.depthAttachment.numSlices = m_pDriver->m_Textures[id].depth;
}
}
else
{
// on GLES there's an OVR extension that allows attaching multiple layers
if(HasExt[OVR_multiview])
{
GLint numViews = 0, startView = 0;
GL.glGetNamedFramebufferAttachmentParameterivEXT(
curDrawFBO, eGL_DEPTH_ATTACHMENT, eGL_FRAMEBUFFER_ATTACHMENT_TEXTURE_NUM_VIEWS_OVR,
&numViews);
GL.glGetNamedFramebufferAttachmentParameterivEXT(
curDrawFBO, eGL_DEPTH_ATTACHMENT,
eGL_FRAMEBUFFER_ATTACHMENT_TEXTURE_BASE_VIEW_INDEX_OVR, &startView);
if(numViews > 1)
{
pipe.framebuffer.drawFBO.depthAttachment.numSlices = numViews;
pipe.framebuffer.drawFBO.depthAttachment.slice = startView;
}
}
}
if(pipe.framebuffer.drawFBO.stencilAttachment.resourceId ==
pipe.framebuffer.drawFBO.depthAttachment.resourceId)
{
pipe.framebuffer.drawFBO.stencilAttachment.slice =
pipe.framebuffer.drawFBO.depthAttachment.slice;
pipe.framebuffer.drawFBO.stencilAttachment.numSlices =
pipe.framebuffer.drawFBO.depthAttachment.numSlices;
}
}
pipe.framebuffer.drawFBO.drawBuffers.resize(numCols);
for(GLint i = 0; i < numCols; i++)
{
GLenum b = eGL_NONE;
drv.glGetIntegerv(GLenum(eGL_DRAW_BUFFER0 + i), (GLint *)&b);
if(b >= eGL_COLOR_ATTACHMENT0 && b <= GLenum(eGL_COLOR_ATTACHMENT0 + numCols))
pipe.framebuffer.drawFBO.drawBuffers[i] = b - eGL_COLOR_ATTACHMENT0;
else
pipe.framebuffer.drawFBO.drawBuffers[i] = -1;
}
pipe.framebuffer.drawFBO.readBuffer = -1;
}
{
GLenum type = eGL_TEXTURE;
for(GLint i = 0; i < numCols; i++)
{
drv.glGetFramebufferAttachmentParameteriv(
eGL_READ_FRAMEBUFFER, GLenum(eGL_COLOR_ATTACHMENT0 + i),
eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, (GLint *)&curCol[i]);
drv.glGetFramebufferAttachmentParameteriv(
eGL_READ_FRAMEBUFFER, GLenum(eGL_COLOR_ATTACHMENT0 + i),
eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, (GLint *)&type);
if(type == eGL_RENDERBUFFER)
rbCol[i] = true;
}
drv.glGetFramebufferAttachmentParameteriv(eGL_READ_FRAMEBUFFER, eGL_DEPTH_ATTACHMENT,
eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME,
(GLint *)&curDepth);
drv.glGetFramebufferAttachmentParameteriv(eGL_READ_FRAMEBUFFER, eGL_DEPTH_ATTACHMENT,
eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, (GLint *)&type);
if(type == eGL_RENDERBUFFER)
rbDepth = true;
drv.glGetFramebufferAttachmentParameteriv(eGL_READ_FRAMEBUFFER, eGL_STENCIL_ATTACHMENT,
eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME,
(GLint *)&curStencil);
drv.glGetFramebufferAttachmentParameteriv(eGL_READ_FRAMEBUFFER, eGL_STENCIL_ATTACHMENT,
eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, (GLint *)&type);
if(type == eGL_RENDERBUFFER)
rbStencil = true;
pipe.framebuffer.readFBO.resourceId =
rm->GetOriginalID(rm->GetResID(FramebufferRes(ctx, curReadFBO)));
pipe.framebuffer.readFBO.colorAttachments.resize(numCols);
for(GLint i = 0; i < numCols; i++)
{
pipe.framebuffer.readFBO.colorAttachments[i].resourceId = rm->GetOriginalID(
rm->GetResID(rbCol[i] ? RenderbufferRes(ctx, curCol[i]) : TextureRes(ctx, curCol[i])));
if(pipe.framebuffer.readFBO.colorAttachments[i].resourceId != ResourceId() && !rbCol[i])
GetFramebufferMipAndLayer(curReadFBO, GLenum(eGL_COLOR_ATTACHMENT0 + i),
(GLint *)&pipe.framebuffer.readFBO.colorAttachments[i].mipLevel,
(GLint *)&pipe.framebuffer.readFBO.colorAttachments[i].slice);
}
pipe.framebuffer.readFBO.depthAttachment.resourceId = rm->GetOriginalID(
rm->GetResID(rbDepth ? RenderbufferRes(ctx, curDepth) : TextureRes(ctx, curDepth)));
pipe.framebuffer.readFBO.stencilAttachment.resourceId = rm->GetOriginalID(
rm->GetResID(rbStencil ? RenderbufferRes(ctx, curStencil) : TextureRes(ctx, curStencil)));
if(pipe.framebuffer.readFBO.depthAttachment.resourceId != ResourceId() && !rbDepth)
GetFramebufferMipAndLayer(curReadFBO, eGL_DEPTH_ATTACHMENT,
(GLint *)&pipe.framebuffer.readFBO.depthAttachment.mipLevel,
(GLint *)&pipe.framebuffer.readFBO.depthAttachment.slice);
if(pipe.framebuffer.readFBO.stencilAttachment.resourceId != ResourceId() && !rbStencil)
GetFramebufferMipAndLayer(curReadFBO, eGL_STENCIL_ATTACHMENT,
(GLint *)&pipe.framebuffer.readFBO.stencilAttachment.mipLevel,
(GLint *)&pipe.framebuffer.readFBO.stencilAttachment.slice);
pipe.framebuffer.readFBO.drawBuffers.resize(numCols);
for(GLint i = 0; i < numCols; i++)
pipe.framebuffer.readFBO.drawBuffers[i] = -1;
GLenum b = eGL_NONE;
drv.glGetIntegerv(eGL_READ_BUFFER, (GLint *)&b);
if(b >= eGL_COLOR_ATTACHMENT0 && b <= GLenum(eGL_COLOR_ATTACHMENT0 + numCols))
pipe.framebuffer.drawFBO.readBuffer = b - eGL_COLOR_ATTACHMENT0;
else
pipe.framebuffer.drawFBO.readBuffer = -1;
}
pipe.framebuffer.blendState.blendFactor = rs.BlendColor;
pipe.framebuffer.framebufferSRGB = rs.Enabled[GLRenderState::eEnabled_FramebufferSRGB];
pipe.framebuffer.dither = rs.Enabled[GLRenderState::eEnabled_Dither];
RDCCOMPILE_ASSERT(ARRAY_COUNT(rs.Blends) == ARRAY_COUNT(rs.ColorMasks),
"Color masks and blends mismatched");
pipe.framebuffer.blendState.blends.resize(ARRAY_COUNT(rs.Blends));
for(size_t i = 0; i < ARRAY_COUNT(rs.Blends); i++)
{
pipe.framebuffer.blendState.blends[i].enabled = rs.Blends[i].Enabled;
pipe.framebuffer.blendState.blends[i].logicOperation = LogicOperation::NoOp;
if(rs.LogicOp != eGL_NONE && rs.LogicOp != eGL_COPY)
pipe.framebuffer.blendState.blends[i].logicOperation = MakeLogicOp(rs.LogicOp);
pipe.framebuffer.blendState.blends[i].logicOperationEnabled =
rs.Enabled[GLRenderState::eEnabled_ColorLogicOp];
pipe.framebuffer.blendState.blends[i].colorBlend.source =
MakeBlendMultiplier(rs.Blends[i].SourceRGB);
pipe.framebuffer.blendState.blends[i].colorBlend.destination =
MakeBlendMultiplier(rs.Blends[i].DestinationRGB);
pipe.framebuffer.blendState.blends[i].colorBlend.operation =
MakeBlendOp(rs.Blends[i].EquationRGB);
pipe.framebuffer.blendState.blends[i].alphaBlend.source =
MakeBlendMultiplier(rs.Blends[i].SourceAlpha);
pipe.framebuffer.blendState.blends[i].alphaBlend.destination =
MakeBlendMultiplier(rs.Blends[i].DestinationAlpha);
pipe.framebuffer.blendState.blends[i].alphaBlend.operation =
MakeBlendOp(rs.Blends[i].EquationAlpha);
pipe.framebuffer.blendState.blends[i].writeMask = 0;
if(rs.ColorMasks[i].red)
pipe.framebuffer.blendState.blends[i].writeMask |= 1;
if(rs.ColorMasks[i].green)
pipe.framebuffer.blendState.blends[i].writeMask |= 2;
if(rs.ColorMasks[i].blue)
pipe.framebuffer.blendState.blends[i].writeMask |= 4;
if(rs.ColorMasks[i].alpha)
pipe.framebuffer.blendState.blends[i].writeMask |= 8;
}
switch(rs.Hints.Derivatives)
{
default:
case eGL_DONT_CARE: pipe.hints.derivatives = QualityHint::DontCare; break;
case eGL_NICEST: pipe.hints.derivatives = QualityHint::Nicest; break;
case eGL_FASTEST: pipe.hints.derivatives = QualityHint::Fastest; break;
}
switch(rs.Hints.LineSmooth)
{
default:
case eGL_DONT_CARE: pipe.hints.lineSmoothing = QualityHint::DontCare; break;
case eGL_NICEST: pipe.hints.lineSmoothing = QualityHint::Nicest; break;
case eGL_FASTEST: pipe.hints.lineSmoothing = QualityHint::Fastest; break;
}
switch(rs.Hints.PolySmooth)
{
default:
case eGL_DONT_CARE: pipe.hints.polySmoothing = QualityHint::DontCare; break;
case eGL_NICEST: pipe.hints.polySmoothing = QualityHint::Nicest; break;
case eGL_FASTEST: pipe.hints.polySmoothing = QualityHint::Fastest; break;
}
switch(rs.Hints.TexCompression)
{
default:
case eGL_DONT_CARE: pipe.hints.textureCompression = QualityHint::DontCare; break;
case eGL_NICEST: pipe.hints.textureCompression = QualityHint::Nicest; break;
case eGL_FASTEST: pipe.hints.textureCompression = QualityHint::Fastest; break;
}
pipe.hints.lineSmoothingEnabled = rs.Enabled[GLRenderState::eEnabled_LineSmooth];
pipe.hints.polySmoothingEnabled = rs.Enabled[GLRenderState::eEnabled_PolySmooth];
}
void GLReplay::OpenGLFillCBufferVariables(ResourceId shader, GLuint prog, bool bufferBacked,
rdcstr prefix, const rdcarray<ShaderConstant> &variables,
rdcarray<ShaderVariable> &outvars,
const bytebuf &bufferData)
{
bytebuf uniformData;
const bytebuf &data = bufferBacked ? bufferData : uniformData;
if(!bufferBacked)
uniformData.resize(128);
for(int32_t i = 0; i < variables.count(); i++)
{
const ShaderConstantDescriptor &desc = variables[i].type.descriptor;
// remove implicit '.' for recursing through "structs" if it's actually a multi-dimensional
// array.
if(!prefix.empty() && prefix.back() == '.' && variables[i].name[0] == '[')
prefix.pop_back();
ShaderVariable var;
var.name = variables[i].name;
var.rows = desc.rows;
var.columns = desc.columns;
var.type = desc.type;
var.rowMajor = desc.rowMajorStorage;
const uint32_t matStride = desc.matrixByteStride;
if(!variables[i].type.members.empty())
{
if(desc.elements == 0)
{
OpenGLFillCBufferVariables(shader, prog, bufferBacked, prefix + var.name.c_str() + ".",
variables[i].type.members, var.members, data);
var.isStruct = true;
}
else
{
var.members.resize(desc.elements);
for(uint32_t a = 0; a < desc.elements; a++)
{
ShaderVariable &arrEl = var.members[a];
arrEl.rows = var.rows;
arrEl.columns = var.columns;
arrEl.name = StringFormat::Fmt("%s[%u]", var.name.c_str(), a);
arrEl.type = var.type;
arrEl.isStruct = true;
arrEl.rowMajor = var.rowMajor;
OpenGLFillCBufferVariables(shader, prog, bufferBacked, prefix + arrEl.name.c_str() + ".",
variables[i].type.members, arrEl.members, data);
}
var.isStruct = false;
var.rows = var.columns = 0;
}
}
else
{
RDCEraseEl(var.value);
// need to query offset and strides as there's no way to know what layout was used
// (and if it's not an std layout it's implementation defined :( )
rdcstr fullname = prefix + var.name;
GLuint idx = GL.glGetProgramResourceIndex(prog, eGL_UNIFORM, fullname.c_str());
if(idx == GL_INVALID_INDEX)
{
// this might not be an error, this might be the corresponding member in an array-of-structs
// that doesn't exist because it's not in a UBO.
// e.g.:
// struct foo { float a; float b; }
// uniform foo bar[2];
//
// If the program only references bar[0].a and bar[1].b then we'd reflect the full structure
// but only bar[0].a and bar[1].b would have indices - bar[0].b and bar[1].a would not.
RDCWARN("Can't find program resource index for %s", fullname.c_str());
if(bufferBacked)
RDCERR("Uniform is buffer backed - index expected");
// if this is an array, generate empty members
if(desc.elements > 0)
{
rdcarray<ShaderVariable> elems;
for(uint32_t a = 0; a < desc.elements; a++)
{
ShaderVariable el = var;
// if this is the last part of a multidimensional array, don't include the variable name
if(var.name[0] != '[')
el.name = StringFormat::Fmt("%s[%u]", var.name.c_str(), a);
else
el.name = StringFormat::Fmt("[%u]", a);
el.isStruct = false;
elems.push_back(el);
}
var.members = elems;
var.isStruct = false;
var.rows = var.columns = 0;
}
}
else
{
GLenum props[] = {bufferBacked ? eGL_OFFSET : eGL_LOCATION};
GLint values[] = {0};
GL.glGetProgramResourceiv(prog, eGL_UNIFORM, idx, ARRAY_COUNT(props), props,
ARRAY_COUNT(props), NULL, values);
GLint location = values[0];
GLint offset = values[0];
if(!bufferBacked)
offset = 0;
if(desc.elements == 0)
{
if(!bufferBacked)
{
switch(var.type)
{
case VarType::Unknown:
case VarType::GPUPointer:
case VarType::ConstantBlock:
case VarType::ReadOnlyResource:
case VarType::ReadWriteResource:
case VarType::Sampler:
case VarType::SLong:
case VarType::ULong:
case VarType::SShort:
case VarType::UShort:
case VarType::SByte:
case VarType::UByte:
case VarType::Half:
RDCERR("Unexpected base variable type %s, treating as float",
ToStr(var.type).c_str());
DELIBERATE_FALLTHROUGH();
case VarType::Float:
GL.glGetUniformfv(prog, location, (float *)uniformData.data());
break;
case VarType::SInt:
GL.glGetUniformiv(prog, location, (int32_t *)uniformData.data());
break;
case VarType::Bool:
case VarType::UInt:
GL.glGetUniformuiv(prog, location, (uint32_t *)uniformData.data());
break;
case VarType::Double:
GL.glGetUniformdv(prog, location, (double *)uniformData.data());
break;
}
}
StandardFillCBufferVariable(shader, desc, offset, data, var, matStride);
}
else
{
rdcarray<ShaderVariable> elems;
for(uint32_t a = 0; a < desc.elements; a++)
{
ShaderVariable el = var;
// if this is the last part of a multidimensional array, don't include the variable name
if(var.name[0] != '[')
el.name = StringFormat::Fmt("%s[%u]", var.name.c_str(), a);
else
el.name = StringFormat::Fmt("[%u]", a);
if(!bufferBacked)
{
switch(var.type)
{
case VarType::Unknown:
case VarType::GPUPointer:
case VarType::ConstantBlock:
case VarType::ReadOnlyResource:
case VarType::ReadWriteResource:
case VarType::Sampler:
case VarType::SLong:
case VarType::ULong:
case VarType::SShort:
case VarType::UShort:
case VarType::SByte:
case VarType::UByte:
case VarType::Half:
RDCERR("Unexpected base variable type %s, treating as float",
ToStr(var.type).c_str());
DELIBERATE_FALLTHROUGH();
case VarType::Float:
GL.glGetUniformfv(prog, location + a, (float *)uniformData.data());
break;
case VarType::SInt:
GL.glGetUniformiv(prog, location + a, (int32_t *)uniformData.data());
break;
case VarType::Bool:
case VarType::UInt:
GL.glGetUniformuiv(prog, location + a, (uint32_t *)uniformData.data());
break;
case VarType::Double:
GL.glGetUniformdv(prog, location + a, (double *)uniformData.data());
break;
}
}
StandardFillCBufferVariable(shader, desc, offset, data, el, matStride);
if(bufferBacked)
offset += desc.arrayByteStride;
el.isStruct = false;
elems.push_back(el);
}
var.members = elems;
var.isStruct = false;
var.rows = var.columns = 0;
}
}
}
outvars.push_back(var);
}
}
void GLReplay::FillCBufferVariables(ResourceId pipeline, ResourceId shader, rdcstr entryPoint,
uint32_t cbufSlot, rdcarray<ShaderVariable> &outvars,
const bytebuf &data)
{
WrappedOpenGL &drv = *m_pDriver;
MakeCurrentReplayContext(&m_ReplayCtx);
auto &shaderDetails = m_pDriver->m_Shaders[shader];
if((int32_t)cbufSlot >= shaderDetails.reflection.constantBlocks.count())
{
RDCERR("Requesting invalid constant block");
return;
}
GLuint curProg = 0;
drv.glGetIntegerv(eGL_CURRENT_PROGRAM, (GLint *)&curProg);
if(curProg == 0)
{
drv.glGetIntegerv(eGL_PROGRAM_PIPELINE_BINDING, (GLint *)&curProg);
if(curProg == 0)
{
RDCERR("No program or pipeline bound");
return;
}
else
{
ResourceId id =
m_pDriver->GetResourceManager()->GetResID(ProgramPipeRes(m_pDriver->GetCtx(), curProg));
auto &pipeDetails = m_pDriver->m_Pipelines[id];
size_t s = ShaderIdx(shaderDetails.type);
curProg =
m_pDriver->GetResourceManager()->GetCurrentResource(pipeDetails.stagePrograms[s]).name;
}
}
const ConstantBlock &cblock = shaderDetails.reflection.constantBlocks[cbufSlot];
if(shaderDetails.spirvWords.empty())
{
OpenGLFillCBufferVariables(shaderDetails.reflection.resourceId, curProg,
cblock.bufferBacked ? true : false, "", cblock.variables, outvars,
data);
}
else
{
if(shaderDetails.mapping.constantBlocks[cbufSlot].bindset == SpecializationConstantBindSet)
{
rdcarray<SpecConstant> specconsts;
for(size_t i = 0; i < shaderDetails.specIDs.size(); i++)
{
SpecConstant spec;
spec.specID = shaderDetails.specIDs[i];
spec.value = shaderDetails.specValues[i];
spec.dataSize = 4;
specconsts.push_back(spec);
}
FillSpecConstantVariables(shaderDetails.reflection.resourceId, cblock.variables, outvars,
specconsts);
}
else if(!cblock.bufferBacked)
{
OpenGLFillCBufferVariables(shaderDetails.reflection.resourceId, curProg, false, "",
cblock.variables, outvars, data);
}
else
{
StandardFillCBufferVariables(shaderDetails.reflection.resourceId, cblock.variables, outvars,
data);
}
}
}
void GLReplay::GetTextureData(ResourceId tex, const Subresource &sub,
const GetTextureDataParams &params, bytebuf &data)
{
WrappedOpenGL &drv = *m_pDriver;
if(m_pDriver->m_Textures.find(tex) == m_pDriver->m_Textures.end())
{
data.clear();
RDCWARN("Requesting data for non-existant texture %s", ToStr(tex).c_str());
return;
}
WrappedOpenGL::TextureData &texDetails = m_pDriver->m_Textures[tex];
GLuint tempTex = 0;
Subresource s = sub;
GLenum texType = texDetails.curType;
GLuint texname = texDetails.resource.name;
int numMips = GetNumMips(texType, texname, texDetails.width, texDetails.height, texDetails.depth);
s.mip = RDCMIN(uint32_t(numMips - 1), s.mip);
GLenum intFormat = texDetails.internalFormat;
GLsizei width = RDCMAX(1, texDetails.width >> s.mip);
GLsizei height = RDCMAX(1, texDetails.height >> s.mip);
GLsizei depth = RDCMAX(1, texDetails.depth >> s.mip);
GLsizei arraysize = 1;
GLint samples = texDetails.samples;
if(texType == eGL_NONE)
{
RDCERR("Trying to get texture data for unknown ID %s!", ToStr(tex).c_str());
return;
}
if(texType == eGL_TEXTURE_BUFFER)
{
GLuint bufName = 0;
drv.glGetTextureLevelParameterivEXT(texname, texType, 0, eGL_TEXTURE_BUFFER_DATA_STORE_BINDING,
(GLint *)&bufName);
ResourceId id =
m_pDriver->GetResourceManager()->GetResID(BufferRes(m_pDriver->GetCtx(), bufName));
GLuint offs = 0, size = 0;
drv.glGetTextureLevelParameterivEXT(texname, texType, 0, eGL_TEXTURE_BUFFER_OFFSET,
(GLint *)&offs);
drv.glGetTextureLevelParameterivEXT(texname, texType, 0, eGL_TEXTURE_BUFFER_SIZE, (GLint *)&size);
GetBufferData(id, offs, size, data);
return;
}
if(texType == eGL_TEXTURE_2D_ARRAY || texType == eGL_TEXTURE_2D_MULTISAMPLE_ARRAY ||
texType == eGL_TEXTURE_1D_ARRAY || texType == eGL_TEXTURE_CUBE_MAP ||
texType == eGL_TEXTURE_CUBE_MAP_ARRAY)
{
// array size doesn't get mip'd down
depth = 1;
arraysize = texDetails.depth;
if(texType == eGL_TEXTURE_1D_ARRAY)
{
height = 1;
arraysize = texDetails.height;
}
if(texType == eGL_TEXTURE_CUBE_MAP)
{
arraysize = 6;
}
}
s.sample = RDCMIN(uint32_t(texDetails.samples - 1), s.sample);
s.slice = RDCMIN(uint32_t(arraysize - 1), s.slice);
if(params.remap != RemapTexture::NoRemap)
{
GLenum remapFormat = eGL_RGBA8;
if(params.remap == RemapTexture::RGBA8)
remapFormat = eGL_RGBA8;
else if(params.remap == RemapTexture::RGBA16)
remapFormat = eGL_RGBA16F;
else if(params.remap == RemapTexture::RGBA32)
remapFormat = eGL_RGBA32F;
CompType typeCast = BaseRemapType(params.typeCast);
if(typeCast == CompType::Typeless && IsSRGBFormat(intFormat))
typeCast = CompType::UNormSRGB;
remapFormat = GetViewCastedFormat(remapFormat, typeCast);
GLenum newtarget = (texType == eGL_TEXTURE_3D ? eGL_TEXTURE_3D : eGL_TEXTURE_2D);
if(intFormat != remapFormat || newtarget != texType)
{
MakeCurrentReplayContext(m_DebugCtx);
GLenum finalFormat = remapFormat;
// create temporary texture of width/height in the new format to render to
drv.glGenTextures(1, &tempTex);
drv.glBindTexture(newtarget, tempTex);
if(newtarget == eGL_TEXTURE_3D)
drv.glTextureImage3DEXT(tempTex, newtarget, 0, finalFormat, width, height, depth, 0,
GetBaseFormat(finalFormat), GetDataType(finalFormat), NULL);
else
drv.glTextureImage2DEXT(tempTex, newtarget, 0, finalFormat, width, height, 0,
GetBaseFormat(finalFormat), GetDataType(finalFormat), NULL);
drv.glTextureParameteriEXT(tempTex, newtarget, eGL_TEXTURE_MAX_LEVEL, 0);
// create temp framebuffer
GLuint fbo = 0;
drv.glGenFramebuffers(1, &fbo);
drv.glBindFramebuffer(eGL_FRAMEBUFFER, fbo);
drv.glTextureParameteriEXT(tempTex, newtarget, eGL_TEXTURE_MIN_FILTER, eGL_NEAREST);
drv.glTextureParameteriEXT(tempTex, newtarget, eGL_TEXTURE_MAG_FILTER, eGL_NEAREST);
drv.glTextureParameteriEXT(tempTex, newtarget, eGL_TEXTURE_WRAP_S, eGL_CLAMP_TO_EDGE);
drv.glTextureParameteriEXT(tempTex, newtarget, eGL_TEXTURE_WRAP_T, eGL_CLAMP_TO_EDGE);
drv.glTextureParameteriEXT(tempTex, newtarget, eGL_TEXTURE_WRAP_R, eGL_CLAMP_TO_EDGE);
if(newtarget == eGL_TEXTURE_3D)
drv.glFramebufferTexture3D(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, eGL_TEXTURE_3D, tempTex,
0, 0);
else if(newtarget == eGL_TEXTURE_2D)
drv.glFramebufferTexture2D(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, newtarget, tempTex, 0);
float col[] = {0.0f, 0.0f, 0.0f, 1.0f};
drv.glClearBufferfv(eGL_COLOR, 0, col);
// render to the temp texture to do the downcast
float oldW = DebugData.outWidth;
float oldH = DebugData.outHeight;
DebugData.outWidth = float(width);
DebugData.outHeight = float(height);
GLenum baseFormat = !IsCompressedFormat(intFormat) ? GetBaseFormat(intFormat) : eGL_RGBA;
TexDisplayFlags flags = eTexDisplay_None;
if(IsUIntFormat(intFormat))
flags = eTexDisplay_RemapUInt;
else if(IsSIntFormat(intFormat))
flags = eTexDisplay_RemapSInt;
else
flags = eTexDisplay_RemapFloat;
for(GLsizei d = 0; d < (newtarget == eGL_TEXTURE_3D ? depth : 1); d++)
{
TextureDisplay texDisplay;
texDisplay.red = texDisplay.green = texDisplay.blue = texDisplay.alpha = true;
texDisplay.hdrMultiplier = -1.0f;
texDisplay.linearDisplayAsGamma = false;
texDisplay.overlay = DebugOverlay::NoOverlay;
texDisplay.flipY = false;
texDisplay.subresource.mip = s.mip;
texDisplay.subresource.sample = params.resolve ? ~0U : s.sample;
texDisplay.subresource.slice = s.slice;
texDisplay.customShaderId = ResourceId();
texDisplay.rangeMin = params.blackPoint;
texDisplay.rangeMax = params.whitePoint;
texDisplay.scale = 1.0f;
texDisplay.resourceId = tex;
texDisplay.typeCast = params.typeCast;
texDisplay.rawOutput = false;
texDisplay.xOffset = 0;
texDisplay.yOffset = 0;
if(newtarget == eGL_TEXTURE_3D)
{
drv.glFramebufferTexture3D(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, eGL_TEXTURE_3D,
tempTex, 0, (GLint)d);
texDisplay.subresource.slice = (uint32_t)d;
}
drv.glViewport(0, 0, width, height);
GLboolean color_mask[4];
drv.glGetBooleanv(eGL_COLOR_WRITEMASK, color_mask);
// for depth, ensure we only write to the red channel, don't write into 'stencil' in green
// with depth data
if(baseFormat == eGL_DEPTH_COMPONENT || baseFormat == eGL_DEPTH_STENCIL)
{
drv.glColorMask(GL_TRUE, GL_FALSE, GL_FALSE, GL_FALSE);
}
RenderTextureInternal(texDisplay, flags);
drv.glColorMask(color_mask[0], color_mask[1], color_mask[2], color_mask[3]);
}
// do one more time for the stencil
if(baseFormat == eGL_DEPTH_STENCIL)
{
TextureDisplay texDisplay;
texDisplay.green = true;
texDisplay.red = texDisplay.blue = texDisplay.alpha = false;
texDisplay.hdrMultiplier = -1.0f;
texDisplay.linearDisplayAsGamma = false;
texDisplay.overlay = DebugOverlay::NoOverlay;
texDisplay.flipY = false;
texDisplay.subresource.mip = s.mip;
texDisplay.subresource.sample = params.resolve ? ~0U : s.sample;
texDisplay.subresource.slice = s.slice;
texDisplay.customShaderId = ResourceId();
texDisplay.rangeMin = params.blackPoint;
texDisplay.rangeMax = params.whitePoint;
texDisplay.scale = 1.0f;
texDisplay.resourceId = tex;
texDisplay.typeCast = CompType::Typeless;
texDisplay.rawOutput = false;
texDisplay.xOffset = 0;
texDisplay.yOffset = 0;
drv.glViewport(0, 0, width, height);
GLboolean color_mask[4];
drv.glGetBooleanv(eGL_COLOR_WRITEMASK, color_mask);
drv.glColorMask(GL_FALSE, GL_TRUE, GL_FALSE, GL_FALSE);
flags = TexDisplayFlags(
flags & ~(eTexDisplay_RemapFloat | eTexDisplay_RemapUInt | eTexDisplay_RemapSInt));
RenderTextureInternal(texDisplay, flags);
drv.glColorMask(color_mask[0], color_mask[1], color_mask[2], color_mask[3]);
}
DebugData.outWidth = oldW;
DebugData.outHeight = oldH;
// rewrite the variables to temporary texture
texType = newtarget;
texname = tempTex;
intFormat = finalFormat;
if(newtarget != eGL_TEXTURE_3D)
depth = 1;
arraysize = 1;
samples = 1;
s.mip = 0;
s.slice = 0;
drv.glDeleteFramebuffers(1, &fbo);
}
}
else if(params.resolve && samples > 1)
{
MakeCurrentReplayContext(m_DebugCtx);
GLuint curDrawFBO = 0;
GLuint curReadFBO = 0;
drv.glGetIntegerv(eGL_DRAW_FRAMEBUFFER_BINDING, (GLint *)&curDrawFBO);
drv.glGetIntegerv(eGL_READ_FRAMEBUFFER_BINDING, (GLint *)&curReadFBO);
// create temporary texture of width/height in same format to render to
drv.glGenTextures(1, &tempTex);
drv.glBindTexture(eGL_TEXTURE_2D, tempTex);
drv.glTextureImage2DEXT(tempTex, eGL_TEXTURE_2D, 0, intFormat, width, height, 0,
GetBaseFormat(intFormat), GetDataType(intFormat), NULL);
drv.glTextureParameteriEXT(tempTex, eGL_TEXTURE_2D, eGL_TEXTURE_MAX_LEVEL, 0);
// create temp framebuffers
GLuint fbos[2] = {0};
drv.glGenFramebuffers(2, fbos);
drv.glBindFramebuffer(eGL_FRAMEBUFFER, fbos[0]);
drv.glFramebufferTexture2D(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, eGL_TEXTURE_2D, tempTex, 0);
drv.glBindFramebuffer(eGL_FRAMEBUFFER, fbos[1]);
if(texType == eGL_TEXTURE_2D_MULTISAMPLE_ARRAY)
drv.glFramebufferTextureLayer(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, texname, 0, s.slice);
else
drv.glFramebufferTexture2D(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, texType, texname, 0);
// do default resolve (framebuffer blit)
drv.glBindFramebuffer(eGL_DRAW_FRAMEBUFFER, fbos[0]);
drv.glBindFramebuffer(eGL_READ_FRAMEBUFFER, fbos[1]);
float col[] = {0.3f, 0.4f, 0.5f, 1.0f};
drv.glClearBufferfv(eGL_COLOR, 0, col);
SafeBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, eGL_NEAREST);
// rewrite the variables to temporary texture
texType = eGL_TEXTURE_2D;
texname = tempTex;
depth = 1;
s.mip = 0;
s.slice = 0;
s.sample = 0;
arraysize = 1;
samples = 1;
drv.glDeleteFramebuffers(2, fbos);
drv.glBindFramebuffer(eGL_DRAW_FRAMEBUFFER, curDrawFBO);
drv.glBindFramebuffer(eGL_READ_FRAMEBUFFER, curReadFBO);
}
else if(samples > 1)
{
MakeCurrentReplayContext(m_DebugCtx);
// copy multisampled texture to an array. This creates tempTex and returns it in that variable,
// for us to own
tempTex = 0;
m_pDriver->CopyTex2DMSToArray(tempTex, texname, width, height, arraysize, samples, intFormat);
// CopyTex2DMSToArray is unwrapped, so register the resource here now
m_pDriver->GetResourceManager()->RegisterResource(TextureRes(m_pDriver->GetCtx(), tempTex));
// rewrite the variables to temporary texture
texType = eGL_TEXTURE_2D_ARRAY;
texname = tempTex;
depth = 1;
arraysize = arraysize * samples;
// remap from slice & sample to just slice, given that each slice is expanded to N slices - one
// for each sample.
s.slice = s.slice * samples + s.sample;
s.sample = 0;
samples = 1;
}
// fetch and return data now
{
MakeCurrentReplayContext(m_DebugCtx);
PixelPackState pack;
pack.Fetch(true);
ResetPixelPackState(true, 1);
if(texType == eGL_RENDERBUFFER)
{
// do blit from renderbuffer to texture
MakeCurrentReplayContext(&m_ReplayCtx);
GLuint curDrawFBO = 0;
GLuint curReadFBO = 0;
drv.glGetIntegerv(eGL_DRAW_FRAMEBUFFER_BINDING, (GLint *)&curDrawFBO);
drv.glGetIntegerv(eGL_READ_FRAMEBUFFER_BINDING, (GLint *)&curReadFBO);
drv.glBindFramebuffer(eGL_DRAW_FRAMEBUFFER, texDetails.renderbufferFBOs[1]);
drv.glBindFramebuffer(eGL_READ_FRAMEBUFFER, texDetails.renderbufferFBOs[0]);
GLenum b = GetBaseFormat(texDetails.internalFormat);
GLbitfield mask = GL_COLOR_BUFFER_BIT;
if(b == eGL_DEPTH_COMPONENT)
mask = GL_DEPTH_BUFFER_BIT;
else if(b == eGL_STENCIL)
mask = GL_STENCIL_BUFFER_BIT;
else if(b == eGL_DEPTH_STENCIL)
mask = GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
SafeBlitFramebuffer(0, 0, texDetails.width, texDetails.height, 0, 0, texDetails.width,
texDetails.height, mask, eGL_NEAREST);
drv.glBindFramebuffer(eGL_DRAW_FRAMEBUFFER, curDrawFBO);
drv.glBindFramebuffer(eGL_READ_FRAMEBUFFER, curReadFBO);
// then proceed to read from the texture
texname = texDetails.renderbufferReadTex;
texType = texDetails.samples > 1 ? eGL_TEXTURE_2D_MULTISAMPLE : eGL_TEXTURE_2D;
MakeCurrentReplayContext(m_DebugCtx);
}
GLenum binding = TextureBinding(texType);
GLuint prevtex = 0;
drv.glGetIntegerv(binding, (GLint *)&prevtex);
drv.glBindTexture(texType, texname);
GLenum target = texType;
if(texType == eGL_TEXTURE_CUBE_MAP)
{
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,
};
RDCASSERT(s.slice < ARRAY_COUNT(targets));
target = targets[s.slice];
// we've "used" the slice, it's not actually a real slice anymore...
s.slice = 0;
arraysize = 1;
}
size_t dataSize = 0;
if(IsCompressedFormat(intFormat))
{
dataSize = (size_t)GetCompressedByteSize(width, height, depth, intFormat);
// contains a single slice
data.resize(dataSize);
// Note that for array textures we fetch the whole mip level (all slices at that mip). Since
// GL returns all slices together, we cache it and keep the data around. This is because in
// many cases we don't just want one slice we want all of them, but to preserve the API
// querying slice-at-a-time we must cache the results of calling glGetTexImage to avoid
// allocating the whole N layers N times.
// check arraysize, since if we remapped or otherwise picked out a slice above, this will now
// be 1 and we don't have to worry about anything
if(arraysize > 1)
{
// if we don't have this texture cached, delete the previous data
// we don't have to use anything else as the cache key, because if we still have an array at
// this point then none of the GetTextureDataParams are relevant - only mip/arrayIdx
if(m_GetTexturePrevID != tex)
{
for(size_t i = 0; i < ARRAY_COUNT(m_GetTexturePrevData); i++)
{
delete[] m_GetTexturePrevData[i];
m_GetTexturePrevData[i] = NULL;
}
}
m_GetTexturePrevID = tex;
RDCASSERT(s.mip < ARRAY_COUNT(m_GetTexturePrevData));
// if we don't have this mip cached, fetch it now
if(m_GetTexturePrevData[s.mip] == NULL)
{
m_GetTexturePrevData[s.mip] = new byte[dataSize * arraysize];
if(IsGLES)
texDetails.GetCompressedImageDataGLES(s.mip, target, dataSize * arraysize,
m_GetTexturePrevData[s.mip]);
else
drv.glGetCompressedTexImage(target, s.mip, m_GetTexturePrevData[s.mip]);
}
// now copy the slice from the cache into ret
byte *src = m_GetTexturePrevData[s.mip];
src += dataSize * s.slice;
memcpy(data.data(), src, dataSize);
}
else
{
// for non-arrays we can just readback without caching
if(IsGLES)
texDetails.GetCompressedImageDataGLES(s.mip, target, dataSize, data.data());
else
drv.glGetCompressedTexImage(target, s.mip, data.data());
}
}
else
{
GLenum fmt = GetBaseFormat(intFormat);
GLenum type = GetDataType(intFormat);
size_t rowSize = GetByteSize(width, 1, 1, fmt, type);
dataSize = GetByteSize(width, height, depth, fmt, type);
data.resize(dataSize);
// see above for the logic of handling arrays
if(arraysize > 1)
{
if(m_GetTexturePrevID != tex)
{
for(size_t i = 0; i < ARRAY_COUNT(m_GetTexturePrevData); i++)
{
delete[] m_GetTexturePrevData[i];
m_GetTexturePrevData[i] = NULL;
}
}
m_GetTexturePrevID = tex;
RDCASSERT(s.mip < ARRAY_COUNT(m_GetTexturePrevData));
// if we don't have this mip cached, fetch it now
if(m_GetTexturePrevData[s.mip] == NULL)
{
m_GetTexturePrevData[s.mip] = new byte[dataSize * arraysize];
drv.glGetTexImage(target, (GLint)s.mip, fmt, type, m_GetTexturePrevData[s.mip]);
}
// now copy the slice from the cache into ret
byte *src = m_GetTexturePrevData[s.mip];
src += dataSize * s.slice;
memcpy(data.data(), src, dataSize);
}
else
{
drv.glGetTexImage(target, (GLint)s.mip, fmt, type, data.data());
}
if(params.standardLayout)
{
// GL puts D24 in the top bits (whether or not there's stencil). We choose to standardise it
// to be in the low bits, so swizzle here. for D24 with no stencil, the stencil bits are
// undefined so we can move them around and it means nothing.
if(intFormat == eGL_DEPTH24_STENCIL8 || intFormat == eGL_DEPTH_COMPONENT24)
{
uint32_t *ptr = (uint32_t *)data.data();
for(GLsizei z = 0; z < depth; z++)
{
for(GLsizei y = 0; y < height; y++)
{
for(GLsizei x = 0; x < width; x++)
{
const uint32_t val = *ptr;
*ptr = (val >> 8) | ((val & 0xff) << 24);
ptr++;
}
}
}
}
// GL's RGBA4/RGB5A1 is BGRA order, but it puts alpha in the bottom bits where we expect it
// in the top
if(intFormat == eGL_RGBA4)
{
uint16_t *ptr = (uint16_t *)data.data();
for(size_t i = 0; i < data.size(); i += sizeof(uint16_t))
{
const uint16_t val = *ptr;
*ptr = (val >> 4) | ((val & 0xf) << 12);
ptr++;
}
}
else if(intFormat == eGL_RGB5_A1)
{
uint16_t *ptr = (uint16_t *)data.data();
for(size_t i = 0; i < data.size(); i += sizeof(uint16_t))
{
const uint16_t val = *ptr;
*ptr = (val >> 1) | ((val & 0x1) << 15);
ptr++;
}
}
}
// if we're saving to disk we make the decision to vertically flip any non-compressed
// images. This is a bit arbitrary, but really origin top-left is common for all disk
// formats so we do this flip from bottom-left origin. We only do this for saving to
// disk so that if we're transferring over the network etc for remote replay, the image
// order is consistent (and we just need to take care to apply an extra vertical flip
// for display when proxying).
if(params.forDiskSave)
{
// need to vertically flip the image now to get conventional row ordering
// we either do this when copying out the slice of interest, or just
// on its own
byte *src, *dst;
byte *row = new byte[rowSize];
size_t sliceSize = GetByteSize(width, height, 1, fmt, type);
// invert all slices in a 3D texture
for(GLsizei d = 0; d < depth; d++)
{
dst = data.data() + d * sliceSize;
src = dst + (height - 1) * rowSize;
for(GLsizei i = 0; i<height>> 1; i++)
{
memcpy(row, src, rowSize);
memcpy(src, dst, rowSize);
memcpy(dst, row, rowSize);
dst += rowSize;
src -= rowSize;
}
}
delete[] row;
}
}
pack.Apply(true);
drv.glBindTexture(texType, prevtex);
}
if(tempTex)
drv.glDeleteTextures(1, &tempTex);
}
void GLReplay::BuildCustomShader(ShaderEncoding sourceEncoding, const bytebuf &source,
const rdcstr &entry, const ShaderCompileFlags &compileFlags,
ShaderStage type, ResourceId &id, rdcstr &errors)
{
BuildTargetShader(sourceEncoding, source, entry, compileFlags, type, id, errors);
}
ResourceId GLReplay::ApplyCustomShader(ResourceId shader, ResourceId texid, const Subresource &sub,
CompType typeCast)
{
if(shader == ResourceId() || texid == ResourceId())
return ResourceId();
auto &texDetails = m_pDriver->m_Textures[texid];
MakeCurrentReplayContext(m_DebugCtx);
CreateCustomShaderTex(texDetails.width, texDetails.height);
m_pDriver->glBindFramebuffer(eGL_FRAMEBUFFER, DebugData.customFBO);
m_pDriver->glFramebufferTexture2D(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, eGL_TEXTURE_2D,
DebugData.customTex, sub.mip);
m_pDriver->glViewport(0, 0, RDCMAX(1, texDetails.width >> sub.mip),
RDCMAX(1, texDetails.height >> sub.mip));
DebugData.outWidth = float(RDCMAX(1, texDetails.width));
DebugData.outHeight = float(RDCMAX(1, texDetails.height));
float clr[] = {0.0f, 0.8f, 0.0f, 0.0f};
m_pDriver->glClearBufferfv(eGL_COLOR, 0, clr);
TextureDisplay disp;
disp.red = disp.green = disp.blue = disp.alpha = true;
disp.flipY = false;
disp.xOffset = 0.0f;
disp.yOffset = 0.0f;
disp.customShaderId = shader;
disp.resourceId = texid;
disp.typeCast = typeCast;
disp.hdrMultiplier = -1.0f;
disp.linearDisplayAsGamma = false;
disp.subresource = sub;
disp.overlay = DebugOverlay::NoOverlay;
disp.rangeMin = 0.0f;
disp.rangeMax = 1.0f;
disp.rawOutput = false;
disp.scale = 1.0f;
RenderTextureInternal(disp, eTexDisplay_MipShift);
return DebugData.CustomShaderTexID;
}
void GLReplay::CreateCustomShaderTex(uint32_t w, uint32_t h)
{
if(DebugData.customTex)
{
uint32_t oldw = 0, oldh = 0;
m_pDriver->glGetTextureLevelParameterivEXT(DebugData.customTex, eGL_TEXTURE_2D, 0,
eGL_TEXTURE_WIDTH, (GLint *)&oldw);
m_pDriver->glGetTextureLevelParameterivEXT(DebugData.customTex, eGL_TEXTURE_2D, 0,
eGL_TEXTURE_HEIGHT, (GLint *)&oldh);
if(oldw == w && oldh == h)
return;
m_pDriver->glDeleteTextures(1, &DebugData.customTex);
DebugData.customTex = 0;
}
uint32_t mips = CalcNumMips((int)w, (int)h, 1);
m_pDriver->glGenTextures(1, &DebugData.customTex);
m_pDriver->glBindTexture(eGL_TEXTURE_2D, DebugData.customTex);
for(uint32_t i = 0; i < mips; i++)
{
m_pDriver->glTextureImage2DEXT(DebugData.customTex, eGL_TEXTURE_2D, i, eGL_RGBA16F,
(GLsizei)RDCMAX(1U, w >> i), (GLsizei)RDCMAX(1U, h >> i), 0,
eGL_RGBA, eGL_FLOAT, NULL);
}
m_pDriver->glTextureParameteriEXT(DebugData.customTex, eGL_TEXTURE_2D, eGL_TEXTURE_MIN_FILTER,
eGL_NEAREST);
m_pDriver->glTextureParameteriEXT(DebugData.customTex, eGL_TEXTURE_2D, eGL_TEXTURE_MAG_FILTER,
eGL_NEAREST);
m_pDriver->glTextureParameteriEXT(DebugData.customTex, eGL_TEXTURE_2D, eGL_TEXTURE_BASE_LEVEL, 0);
m_pDriver->glTextureParameteriEXT(DebugData.customTex, eGL_TEXTURE_2D, eGL_TEXTURE_MAX_LEVEL,
mips - 1);
m_pDriver->glTextureParameteriEXT(DebugData.customTex, eGL_TEXTURE_2D, eGL_TEXTURE_WRAP_S,
eGL_CLAMP_TO_EDGE);
m_pDriver->glTextureParameteriEXT(DebugData.customTex, eGL_TEXTURE_2D, eGL_TEXTURE_WRAP_T,
eGL_CLAMP_TO_EDGE);
DebugData.CustomShaderTexID =
m_pDriver->GetResourceManager()->GetResID(TextureRes(m_pDriver->GetCtx(), DebugData.customTex));
}
void GLReplay::FreeCustomShader(ResourceId id)
{
if(id == ResourceId())
return;
m_pDriver->glDeleteShader(m_pDriver->GetResourceManager()->GetCurrentResource(id).name);
}
void GLReplay::BuildTargetShader(ShaderEncoding sourceEncoding, const bytebuf &source,
const rdcstr &entry, const ShaderCompileFlags &compileFlags,
ShaderStage type, ResourceId &id, rdcstr &errors)
{
WrappedOpenGL &drv = *m_pDriver;
MakeCurrentReplayContext(m_DebugCtx);
GLenum shtype = eGL_VERTEX_SHADER;
switch(type)
{
case ShaderStage::Vertex: shtype = eGL_VERTEX_SHADER; break;
case ShaderStage::Tess_Control: shtype = eGL_TESS_CONTROL_SHADER; break;
case ShaderStage::Tess_Eval: shtype = eGL_TESS_EVALUATION_SHADER; break;
case ShaderStage::Geometry: shtype = eGL_GEOMETRY_SHADER; break;
case ShaderStage::Fragment: shtype = eGL_FRAGMENT_SHADER; break;
case ShaderStage::Compute: shtype = eGL_COMPUTE_SHADER; break;
default:
{
RDCERR("Unknown shader type %u", type);
id = ResourceId();
return;
}
}
const char *src = (const char *)source.data();
GLint len = source.count();
GLuint shader = drv.glCreateShader(shtype);
drv.glShaderSource(shader, 1, &src, &len);
drv.glCompileShader(shader);
GLint status = 0;
drv.glGetShaderiv(shader, eGL_COMPILE_STATUS, &status);
{
len = 1024;
drv.glGetShaderiv(shader, eGL_INFO_LOG_LENGTH, &len);
char *buffer = new char[len + 1];
drv.glGetShaderInfoLog(shader, len, NULL, buffer);
buffer[len] = 0;
errors = buffer;
delete[] buffer;
}
if(status == 0)
id = ResourceId();
else
id = m_pDriver->GetResourceManager()->GetResID(ShaderRes(m_pDriver->GetCtx(), shader));
}
void GLReplay::ReplaceResource(ResourceId from, ResourceId to)
{
MakeCurrentReplayContext(&m_ReplayCtx);
m_pDriver->ReplaceResource(from, to);
ClearPostVSCache();
}
void GLReplay::RemoveReplacement(ResourceId id)
{
MakeCurrentReplayContext(&m_ReplayCtx);
m_pDriver->RemoveReplacement(id);
ClearPostVSCache();
}
void GLReplay::FreeTargetResource(ResourceId id)
{
MakeCurrentReplayContext(&m_ReplayCtx);
m_pDriver->FreeTargetResource(id);
}
ResourceId GLReplay::CreateProxyTexture(const TextureDescription &templateTex)
{
WrappedOpenGL &drv = *m_pDriver;
MakeCurrentReplayContext(m_DebugCtx);
GLenum intFormat = MakeGLFormat(templateTex.format);
bool isCompressed = IsCompressedFormat(intFormat);
GLenum baseFormat = eGL_RGBA;
GLenum dataType = eGL_UNSIGNED_BYTE;
if(!isCompressed)
{
baseFormat = GetBaseFormat(intFormat);
dataType = GetDataType(intFormat);
}
if(baseFormat == eGL_NONE || dataType == eGL_NONE)
return ResourceId();
GLuint tex = 0;
drv.glGenTextures(1, &tex);
GLenum target = eGL_NONE;
switch(templateTex.type)
{
case TextureType::Unknown: break;
case TextureType::Buffer:
case TextureType::Texture1D: target = eGL_TEXTURE_1D; break;
case TextureType::Texture1DArray: target = eGL_TEXTURE_1D_ARRAY; break;
case TextureType::TextureRect:
case TextureType::Texture2D: target = eGL_TEXTURE_2D; break;
case TextureType::Texture2DArray: target = eGL_TEXTURE_2D_ARRAY; break;
case TextureType::Texture2DMS: target = eGL_TEXTURE_2D_MULTISAMPLE; break;
case TextureType::Texture2DMSArray: target = eGL_TEXTURE_2D_MULTISAMPLE_ARRAY; break;
case TextureType::Texture3D: target = eGL_TEXTURE_3D; break;
case TextureType::TextureCube: target = eGL_TEXTURE_CUBE_MAP; break;
case TextureType::TextureCubeArray: target = eGL_TEXTURE_CUBE_MAP_ARRAY; break;
case TextureType::Count: RDCERR("Invalid texture dimension"); break;
}
if(target != eGL_NONE)
{
drv.glBindTexture(target, tex);
if(target == eGL_TEXTURE_2D_MULTISAMPLE)
{
drv.glTextureStorage2DMultisampleEXT(tex, target, templateTex.msSamp, intFormat,
templateTex.width, templateTex.height, GL_TRUE);
}
else if(target == eGL_TEXTURE_2D_MULTISAMPLE_ARRAY)
{
drv.glTextureStorage3DMultisampleEXT(tex, target, templateTex.msSamp, intFormat,
templateTex.width, templateTex.height,
templateTex.arraysize, GL_TRUE);
}
else
{
GLsizei w = (GLsizei)templateTex.width;
GLsizei h = (GLsizei)templateTex.height;
GLsizei d = (GLsizei)templateTex.depth;
int dim = (int)templateTex.dimension;
if(target == eGL_TEXTURE_1D_ARRAY)
{
h = templateTex.arraysize;
dim = 2;
}
else if(target == eGL_TEXTURE_2D_ARRAY || target == eGL_TEXTURE_CUBE_MAP_ARRAY)
{
d = templateTex.arraysize;
dim = 3;
}
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(target != eGL_TEXTURE_CUBE_MAP)
{
targets[0] = target;
count = 1;
}
for(int m = 0; m < (int)templateTex.mips; m++)
{
for(int t = 0; t < count; t++)
{
if(isCompressed)
{
GLsizei compSize = (GLsizei)GetCompressedByteSize(w, h, d, intFormat);
bytebuf dummy;
dummy.resize(compSize);
if(dim == 1)
drv.glCompressedTextureImage1DEXT(tex, targets[t], m, intFormat, w, 0, compSize,
&dummy[0]);
else if(dim == 2)
drv.glCompressedTextureImage2DEXT(tex, targets[t], m, intFormat, w, h, 0, compSize,
&dummy[0]);
else if(dim == 3)
drv.glCompressedTextureImage3DEXT(tex, targets[t], m, intFormat, w, h, d, 0, compSize,
&dummy[0]);
}
else
{
if(dim == 1)
drv.glTextureImage1DEXT(tex, targets[t], m, intFormat, w, 0, baseFormat, dataType,
NULL);
else if(dim == 2)
drv.glTextureImage2DEXT(tex, targets[t], m, intFormat, w, h, 0, baseFormat, dataType,
NULL);
else if(dim == 3)
drv.glTextureImage3DEXT(tex, targets[t], m, intFormat, w, h, d, 0, baseFormat,
dataType, NULL);
}
}
w = RDCMAX(1, w >> 1);
if(target != eGL_TEXTURE_1D_ARRAY)
h = RDCMAX(1, h >> 1);
if(target != eGL_TEXTURE_2D_ARRAY && target != eGL_TEXTURE_CUBE_MAP_ARRAY)
d = RDCMAX(1, d >> 1);
}
}
drv.glTextureParameteriEXT(tex, target, eGL_TEXTURE_MAX_LEVEL, templateTex.mips - 1);
}
// Swizzle R/B channels only for non BGRA textures
if(templateTex.format.BGRAOrder() && target != eGL_NONE && baseFormat != eGL_BGRA)
{
if(HasExt[ARB_texture_swizzle] || HasExt[EXT_texture_swizzle])
{
GLint bgraSwizzle[] = {eGL_BLUE, eGL_GREEN, eGL_RED, eGL_ALPHA};
GLint bgrSwizzle[] = {eGL_BLUE, eGL_GREEN, eGL_RED, eGL_ONE};
if(templateTex.format.compCount == 4)
SetTextureSwizzle(tex, target, (GLenum *)bgraSwizzle);
else if(templateTex.format.compCount == 3)
SetTextureSwizzle(tex, target, (GLenum *)bgrSwizzle);
else
RDCERR("Unexpected component count %d for BGRA order format", templateTex.format.compCount);
}
else
{
RDCERR("Can't create a BGRA proxy texture without texture swizzle extension");
}
}
ResourceId id = m_pDriver->GetResourceManager()->GetResID(TextureRes(m_pDriver->GetCtx(), tex));
return id;
}
void GLReplay::SetProxyTextureData(ResourceId texid, const Subresource &sub, byte *data,
size_t dataSize)
{
WrappedOpenGL &drv = *m_pDriver;
GLuint tex = m_pDriver->GetResourceManager()->GetCurrentResource(texid).name;
auto &texdetails = m_pDriver->m_Textures[texid];
if(texdetails.curType == eGL_NONE)
return;
GLenum fmt = texdetails.internalFormat;
GLenum target = texdetails.curType;
GLint depth = 1;
if(target == eGL_TEXTURE_3D)
depth = RDCMAX(1, texdetails.depth >> sub.mip);
GLint width = RDCMAX(1, texdetails.width >> sub.mip);
GLint height = RDCMAX(1, texdetails.height >> sub.mip);
GLint mip =
RDCMIN((GLint)sub.mip,
GetNumMips(target, tex, texdetails.width, texdetails.height, texdetails.depth) - 1);
GLint slice = (GLint)sub.slice;
GLint sample = RDCMIN((GLint)sub.sample, texdetails.samples - 1);
if(target == eGL_TEXTURE_1D_ARRAY)
slice = RDCMIN(slice, texdetails.height);
if(target == eGL_TEXTURE_2D_ARRAY || target == eGL_TEXTURE_2D_MULTISAMPLE_ARRAY ||
target == eGL_TEXTURE_CUBE_MAP_ARRAY)
slice = RDCMIN(slice, texdetails.depth);
if(target == eGL_TEXTURE_1D_ARRAY)
height = 1;
if(IsCompressedFormat(fmt))
{
PixelUnpackState unpack;
unpack.Fetch(true);
ResetPixelUnpackState(true, 1);
if(target == eGL_TEXTURE_1D)
{
drv.glCompressedTextureSubImage1DEXT(tex, target, mip, 0, width, fmt, (GLsizei)dataSize, data);
}
else if(target == eGL_TEXTURE_1D_ARRAY)
{
drv.glCompressedTextureSubImage2DEXT(tex, target, mip, 0, slice, width, 1, fmt,
(GLsizei)dataSize, data);
}
else if(target == eGL_TEXTURE_2D)
{
drv.glCompressedTextureSubImage2DEXT(tex, target, mip, 0, 0, width, height, fmt,
(GLsizei)dataSize, data);
}
else if(target == eGL_TEXTURE_2D_ARRAY || target == eGL_TEXTURE_CUBE_MAP_ARRAY)
{
drv.glCompressedTextureSubImage3DEXT(tex, target, mip, 0, 0, slice, width, height, 1, fmt,
(GLsizei)dataSize, data);
}
else if(target == eGL_TEXTURE_3D)
{
drv.glCompressedTextureSubImage3DEXT(tex, target, mip, 0, 0, 0, width, height, depth, fmt,
(GLsizei)dataSize, data);
}
else if(target == eGL_TEXTURE_CUBE_MAP)
{
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,
};
target = targets[RDCMIN(slice, GLint(ARRAY_COUNT(targets) - 1))];
drv.glCompressedTextureSubImage2DEXT(tex, target, mip, 0, 0, width, height, fmt,
(GLsizei)dataSize, data);
}
else if(target == eGL_TEXTURE_2D_MULTISAMPLE || target == eGL_TEXTURE_2D_MULTISAMPLE_ARRAY)
{
RDCERR("Unexpected compressed MSAA texture!");
}
unpack.Apply(true);
}
else
{
GLenum baseformat = GetBaseFormat(fmt);
GLenum datatype = GetDataType(fmt);
if(dataSize < GetByteSize(width, height, depth, baseformat, datatype))
{
RDCERR("Insufficient data provided to SetProxyTextureData");
return;
}
PixelUnpackState unpack;
unpack.Fetch(false);
ResetPixelUnpackState(false, 1);
bytebuf swizzled;
// GL puts D24 in the top bits (whether or not there's stencil). We choose to standardise it
// to be in the low bits, so swizzle here. for D24 with no stencil, the stencil bits are
// undefined so we can move them around and it means nothing.
if(texdetails.internalFormat == eGL_DEPTH24_STENCIL8 ||
texdetails.internalFormat == eGL_DEPTH_COMPONENT24)
{
const uint32_t *srcptr = (const uint32_t *)data;
swizzled.resize(dataSize);
uint32_t *dstptr = (uint32_t *)swizzled.data();
for(size_t i = 0; i < dataSize; i += 4)
{
const uint32_t val = *srcptr;
*dstptr = (val << 8) | ((val & 0xff000000) >> 24);
srcptr++;
dstptr++;
}
data = swizzled.data();
}
// GL's RGBA4/RGB5A1 is BGRA order, but it puts alpha in the bottom bits where we expect it
// in the top
else if(texdetails.internalFormat == eGL_RGBA4)
{
const uint16_t *srcptr = (const uint16_t *)data;
swizzled.resize(dataSize);
uint16_t *dstptr = (uint16_t *)swizzled.data();
for(size_t i = 0; i < dataSize; i += 2)
{
const uint16_t val = *srcptr;
*dstptr = ((val & 0x0fff) << 4) | ((val & 0xf000) >> 12);
srcptr++;
dstptr++;
}
data = swizzled.data();
}
else if(texdetails.internalFormat == eGL_RGB5_A1)
{
const uint16_t *srcptr = (const uint16_t *)data;
swizzled.resize(dataSize);
uint16_t *dstptr = (uint16_t *)swizzled.data();
for(size_t i = 0; i < dataSize; i += 2)
{
const uint16_t val = *srcptr;
*dstptr = ((val & 0x7fff) << 1) | ((val & 0x8000) >> 12);
srcptr++;
dstptr++;
}
data = swizzled.data();
}
if(target == eGL_TEXTURE_1D)
{
drv.glTextureSubImage1DEXT(tex, target, mip, 0, width, baseformat, datatype, data);
}
else if(target == eGL_TEXTURE_1D_ARRAY)
{
drv.glTextureSubImage2DEXT(tex, target, mip, 0, slice, width, 1, baseformat, datatype, data);
}
else if(target == eGL_TEXTURE_2D)
{
drv.glTextureSubImage2DEXT(tex, target, mip, 0, 0, width, height, baseformat, datatype, data);
}
else if(target == eGL_TEXTURE_2D_ARRAY || target == eGL_TEXTURE_CUBE_MAP_ARRAY)
{
drv.glTextureSubImage3DEXT(tex, target, mip, 0, 0, slice, width, height, 1, baseformat,
datatype, data);
}
else if(target == eGL_TEXTURE_3D)
{
drv.glTextureSubImage3DEXT(tex, target, mip, 0, 0, 0, width, height, depth, baseformat,
datatype, data);
}
else if(target == eGL_TEXTURE_CUBE_MAP)
{
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,
};
target = targets[RDCMIN(slice, GLint(ARRAY_COUNT(targets) - 1))];
drv.glTextureSubImage2DEXT(tex, target, mip, 0, 0, width, height, baseformat, datatype, data);
}
else if(target == eGL_TEXTURE_2D_MULTISAMPLE || target == eGL_TEXTURE_2D_MULTISAMPLE_ARRAY)
{
// create a temporary 2D array texture to upload the data to. Must use TexStorage to allow
// texture views inside CopyArrayToTex2DMS
GLuint uploadTex = 0;
drv.glGenTextures(1, &uploadTex);
drv.glBindTexture(eGL_TEXTURE_2D_ARRAY, uploadTex);
drv.glTextureStorage3DEXT(uploadTex, eGL_TEXTURE_2D_ARRAY, 1, texdetails.internalFormat,
width, height, texdetails.samples * RDCMAX(1, texdetails.depth));
drv.glTextureParameteriEXT(uploadTex, eGL_TEXTURE_2D_ARRAY, eGL_TEXTURE_MAX_LEVEL, 0);
GLint unpackedSlice = slice * texdetails.samples + sample;
// upload the data to the given slice
drv.glTextureSubImage3DEXT(uploadTex, eGL_TEXTURE_2D_ARRAY, 0, 0, 0, unpackedSlice, width,
height, 1, baseformat, datatype, data);
// copy this slice into the 2D MSAA texture
m_pDriver->CopyArrayToTex2DMS(tex, uploadTex, width, height, texdetails.depth,
texdetails.samples, texdetails.internalFormat, unpackedSlice);
// delete the temporary texture
drv.glDeleteTextures(1, &uploadTex);
}
unpack.Apply(false);
}
}
bool GLReplay::IsTextureSupported(const TextureDescription &tex)
{
// GL can't decide if these formats are BGRA or RGBA order.
// The bit order in memory for e.g. R4G4B4A4 is:
// 15 .. .. 0
// R G B A
//
// but if you upload bits in that order with GL_RGBA it gets flipped.
// It's more reliable to report no support and force a remap
switch(tex.format.type)
{
case ResourceFormatType::R4G4:
case ResourceFormatType::R4G4B4A4:
case ResourceFormatType::R5G6B5:
case ResourceFormatType::R5G5B5A1: return false;
default: break;
}
// We couldn't create proxy textures for ASTC textures (see MakeGLFormat). So we give back false
// and let RemapProxyTextureIfNeeded to set remap type for them.
if(tex.format.type == ResourceFormatType::ASTC)
return false;
// we don't try to replay alpha8 textures, as we stick strictly to core profile GL
if(tex.format.type == ResourceFormatType::A8)
return false;
// don't support 1D/3D block compressed textures
if(tex.dimension != 2 &&
(tex.format.type == ResourceFormatType::BC1 || tex.format.type == ResourceFormatType::BC2 ||
tex.format.type == ResourceFormatType::BC3 || tex.format.type == ResourceFormatType::BC4 ||
tex.format.type == ResourceFormatType::BC5 || tex.format.type == ResourceFormatType::BC6 ||
tex.format.type == ResourceFormatType::BC7 || tex.format.type == ResourceFormatType::ASTC ||
tex.format.type == ResourceFormatType::ETC2 || tex.format.type == ResourceFormatType::EAC))
return false;
// don't support 3D depth textures
if(tex.dimension == 3 &&
(tex.format.compType == CompType::Depth || tex.format.type == ResourceFormatType::D16S8 ||
tex.format.type == ResourceFormatType::D24S8 || tex.format.type == ResourceFormatType::D32S8))
return false;
GLenum fmt = MakeGLFormat(tex.format);
if(fmt == eGL_NONE)
return false;
// BGRA is not accepted as an internal format in case of GL
// EXT_texture_format_BGRA8888 is required for creating BGRA proxy textures in case of GLES
if(fmt == eGL_BGRA8_EXT && (!IsGLES || !HasExt[EXT_texture_format_BGRA8888]))
return false;
GLenum target = eGL_TEXTURE_2D;
switch(tex.type)
{
case TextureType::Unknown: break;
case TextureType::Buffer:
case TextureType::Texture1D: target = eGL_TEXTURE_1D; break;
case TextureType::Texture1DArray: target = eGL_TEXTURE_1D_ARRAY; break;
case TextureType::TextureRect:
case TextureType::Texture2D: target = eGL_TEXTURE_2D; break;
case TextureType::Texture2DArray: target = eGL_TEXTURE_2D_ARRAY; break;
case TextureType::Texture2DMS: target = eGL_TEXTURE_2D_MULTISAMPLE; break;
case TextureType::Texture2DMSArray: target = eGL_TEXTURE_2D_MULTISAMPLE_ARRAY; break;
case TextureType::Texture3D: target = eGL_TEXTURE_3D; break;
case TextureType::TextureCube: target = eGL_TEXTURE_CUBE_MAP; break;
case TextureType::TextureCubeArray: target = eGL_TEXTURE_CUBE_MAP_ARRAY; break;
case TextureType::Count: RDCERR("Invalid texture dimension"); break;
}
GLint supported = 0, fragment = 0;
m_pDriver->glGetInternalformativ(target, fmt, eGL_INTERNALFORMAT_SUPPORTED, 4, &supported);
m_pDriver->glGetInternalformativ(target, fmt, eGL_FRAGMENT_TEXTURE, 4, &fragment);
// check the texture is supported
if(supported == 0 || fragment == 0)
return false;
// for multisampled textures it must be in a view compatibility class, to let us copy to/from the
// MSAA texture.
if(tex.msSamp > 1 && !IsDepthStencilFormat(fmt))
{
GLenum viewClass = eGL_NONE;
m_pDriver->glGetInternalformativ(eGL_TEXTURE_2D_ARRAY, fmt, eGL_VIEW_COMPATIBILITY_CLASS,
sizeof(GLenum), (GLint *)&viewClass);
if(viewClass == eGL_NONE)
return false;
}
return true;
}
bool GLReplay::NeedRemapForFetch(const ResourceFormat &format)
{
if(format.compType == CompType::Depth || format.type == ResourceFormatType::D16S8 ||
format.type == ResourceFormatType::D24S8 || format.type == ResourceFormatType::D32S8)
return IsGLES && !HasExt[NV_read_depth];
return false;
}
ResourceId GLReplay::CreateProxyBuffer(const BufferDescription &templateBuf)
{
WrappedOpenGL &drv = *m_pDriver;
MakeCurrentReplayContext(m_DebugCtx);
GLenum target = eGL_ARRAY_BUFFER;
if(templateBuf.creationFlags & BufferCategory::Indirect)
target = eGL_DRAW_INDIRECT_BUFFER;
if(templateBuf.creationFlags & BufferCategory::Index)
target = eGL_ELEMENT_ARRAY_BUFFER;
if(templateBuf.creationFlags & BufferCategory::Constants)
target = eGL_UNIFORM_BUFFER;
if(templateBuf.creationFlags & BufferCategory::ReadWrite)
target = eGL_SHADER_STORAGE_BUFFER;
GLuint buf = 0;
drv.glGenBuffers(1, &buf);
drv.glBindBuffer(target, buf);
drv.glNamedBufferDataEXT(buf, (GLsizeiptr)templateBuf.length, NULL, eGL_DYNAMIC_DRAW);
ResourceId id = m_pDriver->GetResourceManager()->GetResID(BufferRes(m_pDriver->GetCtx(), buf));
return id;
}
void GLReplay::SetProxyBufferData(ResourceId bufid, byte *data, size_t dataSize)
{
GLuint buf = m_pDriver->GetResourceManager()->GetCurrentResource(bufid).name;
m_pDriver->glNamedBufferSubDataEXT(buf, 0, dataSize, data);
}
rdcarray<EventUsage> GLReplay::GetUsage(ResourceId id)
{
return m_pDriver->GetUsage(id);
}
rdcarray<PixelModification> GLReplay::PixelHistory(rdcarray<EventUsage> events, ResourceId target,
uint32_t x, uint32_t y, const Subresource &sub,
CompType typeCast)
{
GLNOTIMP("GLReplay::PixelHistory");
return {};
}
ShaderDebugTrace *GLReplay::DebugVertex(uint32_t eventId, uint32_t vertid, uint32_t instid,
uint32_t idx, uint32_t view)
{
GLNOTIMP("DebugVertex");
return new ShaderDebugTrace();
}
ShaderDebugTrace *GLReplay::DebugPixel(uint32_t eventId, uint32_t x, uint32_t y, uint32_t sample,
uint32_t primitive)
{
GLNOTIMP("DebugPixel");
return new ShaderDebugTrace();
}
ShaderDebugTrace *GLReplay::DebugThread(uint32_t eventId, const rdcfixedarray<uint32_t, 3> &groupid,
const rdcfixedarray<uint32_t, 3> &threadid)
{
GLNOTIMP("DebugThread");
return new ShaderDebugTrace();
}
rdcarray<ShaderDebugState> GLReplay::ContinueDebug(ShaderDebugger *debugger)
{
GLNOTIMP("ContinueDebug");
return {};
}
void GLReplay::FreeDebugger(ShaderDebugger *debugger)
{
delete debugger;
}
void GLReplay::MakeCurrentReplayContext(GLWindowingData *ctx)
{
static GLWindowingData *prev = NULL;
if(ctx && ctx != prev)
{
#if ENABLED(RDOC_APPLE)
GL.glFinish();
#endif
m_pDriver->m_Platform.MakeContextCurrent(*ctx);
prev = ctx;
m_pDriver->ActivateContext(*ctx);
}
}
void GLReplay::SwapBuffers(GLWindowingData *ctx)
{
m_pDriver->m_Platform.SwapBuffers(*ctx);
}
void GLReplay::CloseReplayContext()
{
m_pDriver->UnregisterReplayContext(m_ReplayCtx);
}
ReplayStatus CreateReplayDevice(RDCDriver rdcdriver, RDCFile *rdc, const ReplayOptions &opts,
GLPlatform &platform, IReplayDriver **&driver)
{
GLInitParams initParams;
uint64_t ver = GLInitParams::CurrentVersion;
// if we have an RDCFile, open the frame capture section and serialise the init params.
// if not, we're creating a proxy-capable device so use default-initialised init params.
if(rdc)
{
int sectionIdx = rdc->SectionIndex(SectionType::FrameCapture);
if(sectionIdx < 0)
return ReplayStatus::InternalError;
ver = rdc->GetSectionProperties(sectionIdx).version;
if(!GLInitParams::IsSupportedVersion(ver))
{
RDCERR("Incompatible OpenGL serialise version %llu", ver);
return ReplayStatus::APIIncompatibleVersion;
}
StreamReader *reader = rdc->ReadSection(sectionIdx);
ReadSerialiser ser(reader, Ownership::Stream);
ser.SetVersion(ver);
SystemChunk chunk = ser.ReadChunk<SystemChunk>();
if(chunk != SystemChunk::DriverInit)
{
RDCERR("Expected to get a DriverInit chunk, instead got %u", chunk);
return ReplayStatus::FileCorrupted;
}
SERIALISE_ELEMENT(initParams);
if(ser.IsErrored())
{
RDCERR("Failed reading driver init params.");
return ReplayStatus::FileIOFailed;
}
if(!initParams.renderer.empty())
RDCLOG("Capture was created on %s / %s", initParams.renderer.c_str(),
initParams.version.c_str());
}
GLWindowingData data = {};
ReplayStatus status = platform.InitialiseAPI(data, rdcdriver, opts.apiValidation);
// any errors will be already printed, just pass the error up
if(status != ReplayStatus::Succeeded)
return status;
bool current = platform.MakeContextCurrent(data);
if(!current)
{
RDCERR("Couldn't active the created GL ES context");
platform.DeleteReplayContext(data);
return ReplayStatus::APIInitFailed;
}
// we use the platform's function which tries GL's GetProcAddress first, then falls back to
// regular function lookup
GL.PopulateWithCallback([&platform](const char *func) { return platform.GetReplayFunction(func); });
FetchEnabledExtensions();
// see gl_emulated.cpp
GL.EmulateUnsupportedFunctions();
GL.EmulateRequiredExtensions();
bool extensionsValidated = CheckReplayContext();
if(!extensionsValidated)
{
platform.DeleteReplayContext(data);
return ReplayStatus::APIInitFailed;
}
bool functionsValidated = ValidateFunctionPointers();
if(!functionsValidated)
{
platform.DeleteReplayContext(data);
return ReplayStatus::APIHardwareUnsupported;
}
WrappedOpenGL *gldriver = new WrappedOpenGL(platform);
gldriver->SetDriverType(rdcdriver);
GL.DriverForEmulation(gldriver);
RDCLOG("Created %s replay device.", ToStr(rdcdriver).c_str());
GLReplay *replay = gldriver->GetReplay();
replay->SetProxy(rdc == NULL);
replay->SetReplayData(data);
if(!replay->HasDebugContext())
{
delete gldriver;
platform.DeleteReplayContext(data);
return ReplayStatus::APIHardwareUnsupported;
}
gldriver->Initialise(initParams, ver, opts);
*driver = (IReplayDriver *)replay;
return ReplayStatus::Succeeded;
}
void GL_ProcessStructured(RDCFile *rdc, SDFile &output)
{
GLDummyPlatform dummy;
WrappedOpenGL device(dummy);
int sectionIdx = rdc->SectionIndex(SectionType::FrameCapture);
if(sectionIdx < 0)
return;
device.SetStructuredExport(rdc->GetSectionProperties(sectionIdx).version);
ReplayStatus status = device.ReadLogInitialisation(rdc, true);
if(status == ReplayStatus::Succeeded)
device.GetStructuredFile().Swap(output);
}
static StructuredProcessRegistration GLProcessRegistration(RDCDriver::OpenGL, &GL_ProcessStructured);
static StructuredProcessRegistration GLESProcessRegistration(RDCDriver::OpenGLES,
&GL_ProcessStructured);
rdcarray<GLVersion> GetReplayVersions(RDCDriver api)
{
// try to create all versions from highest down to lowest in order to get the highest versioned
// context we can
if(api == RDCDriver::OpenGLES)
{
return {
{3, 2}, {3, 1}, {3, 0},
};
}
else
{
return {
{4, 6}, {4, 5}, {4, 4}, {4, 3}, {4, 2}, {4, 1}, {4, 0}, {3, 3}, {3, 2},
};
}
}
#if defined(RENDERDOC_SUPPORT_GLES)
ReplayStatus GLES_CreateReplayDevice(RDCFile *rdc, const ReplayOptions &opts, IReplayDriver **driver)
{
RDCLOG("Creating an OpenGL ES replay device");
// for GLES replay, we try to use EGL if it's available. If it's not available, we look to see if
// we can create an OpenGL ES context via the platform GL functions
if(GetEGLPlatform().CanCreateGLESContext())
{
bool load_ok = GetEGLPlatform().PopulateForReplay();
if(!load_ok)
{
RDCERR("Couldn't find required EGL function addresses");
return ReplayStatus::APIInitFailed;
}
RDCLOG("Initialising GLES replay via libEGL");
return CreateReplayDevice(rdc ? rdc->GetDriver() : RDCDriver::OpenGLES, rdc, opts,
GetEGLPlatform(), driver);
}
#if defined(RENDERDOC_SUPPORT_GL)
else if(GetGLPlatform().CanCreateGLESContext())
{
RDCLOG("libEGL is not available, falling back to EXT_create_context_es2_profile");
bool load_ok = GetGLPlatform().PopulateForReplay();
if(!load_ok)
{
RDCERR("Couldn't find required GLX function addresses");
return ReplayStatus::APIInitFailed;
}
return CreateReplayDevice(rdc ? rdc->GetDriver() : RDCDriver::OpenGLES, rdc, opts,
GetGLPlatform(), driver);
}
RDCERR(
"libEGL not available, and GL cannot initialise or doesn't support "
"EXT_create_context_es2_profile");
return ReplayStatus::APIInitFailed;
#else
// no GL support, no fallback apart from EGL
RDCERR("libEGL is not available");
return ReplayStatus::APIInitFailed;
#endif
}
static DriverRegistration GLESDriverRegistration(RDCDriver::OpenGLES, &GLES_CreateReplayDevice);
#endif
#if defined(RENDERDOC_SUPPORT_GL)
ReplayStatus GL_CreateReplayDevice(RDCFile *rdc, const ReplayOptions &opts, IReplayDriver **driver)
{
GLPlatform *gl_platform = &GetGLPlatform();
if(RenderDoc::Inst().GetGlobalEnvironment().waylandDisplay)
{
#if defined(RENDERDOC_SUPPORT_EGL)
RDCLOG("Forcing EGL device creation for wayland");
gl_platform = &GetEGLPlatform();
#else
RDCERR("EGL support must be enabled at build time when using Wayland");
return ReplayStatus::InternalError;
#endif
}
bool can_create_gl_context = gl_platform->CanCreateGLContext();
#if defined(RENDERDOC_SUPPORT_EGL)
if(!can_create_gl_context && gl_platform == &GetGLPlatform())
{
RDCLOG("Cannot create GL context with GL platform, falling back to EGL");
gl_platform = &GetEGLPlatform();
can_create_gl_context = gl_platform->CanCreateGLContext();
}
#endif
if(!can_create_gl_context)
{
RDCERR("Platform doesn't support GL contexts");
return ReplayStatus::APIInitFailed;
}
RDCDEBUG("Creating an OpenGL replay device");
bool load_ok = gl_platform->PopulateForReplay();
if(!load_ok)
{
RDCERR("Couldn't find required platform %s function addresses",
gl_platform == &GetGLPlatform() ? "GL" : "EGL");
return ReplayStatus::APIInitFailed;
}
return CreateReplayDevice(rdc ? rdc->GetDriver() : RDCDriver::OpenGL, rdc, opts, *gl_platform,
driver);
}
static DriverRegistration GLDriverRegistration(RDCDriver::OpenGL, &GL_CreateReplayDevice);
#endif