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

4576 lines
156 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015-2026 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 "data/glsl_shaders.h"
#include "driver/ihv/amd/amd_counters.h"
#include "driver/ihv/arm/arm_counters.h"
#include "driver/ihv/intel/intel_gl_counters.h"
#include "driver/ihv/nv/nv_gl_counters.h"
#include "maths/matrix.h"
#include "replay/dummy_driver.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;
RenderDoc::Inst().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);
#if DISABLED(RDOC_ANDROID) && DISABLED(RDOC_APPLE)
SAFE_DELETE(m_pNVCounters);
#endif
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;
}
RDResult GLReplay::FatalErrorCheck()
{
return m_pDriver->FatalErrorCheck();
}
IReplayDriver *GLReplay::MakeDummyDriver()
{
// gather up the shaders we've allocated to pass to the dummy driver
rdcarray<const ShaderReflection *> shaders;
for(auto it = m_pDriver->m_Shaders.begin(); it != m_pDriver->m_Shaders.end(); it++)
{
shaders.push_back(it->second.StealReflection());
}
IReplayDriver *dummy = new DummyDriver(this, shaders, m_pDriver->DetachStructuredFile());
return dummy;
}
RDResult 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;
}
}
SDFile *GLReplay::GetStructuredFile()
{
return m_pDriver->GetStructuredFile();
}
rdcarray<uint32_t> GLReplay::GetPassEvents(uint32_t eventId)
{
rdcarray<uint32_t> passEvents;
const ActionDescription *action = m_pDriver->GetAction(eventId);
const ActionDescription *start = action;
while(start && start->previous && !(start->previous->flags & ActionFlags::Clear))
{
const ActionDescription *prev = start->previous;
if(start->outputs != prev->outputs || start->depthOut != prev->depthOut)
break;
start = prev;
}
while(start)
{
if(start == action)
break;
if(start->flags & ActionFlags::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;
}
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.pixelHistory = true;
// we require storage buffers and compute shaders
ret.shaderDebugging = HasExt[ARB_shader_storage_buffer_object] && HasExt[ARB_compute_shader];
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;
#if DISABLED(RDOC_ANDROID) && DISABLED(RDOC_APPLE)
NVGLCounters *countersNV = NULL;
#endif
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 || m_DriverInfo.vendor == GPUVendor::Samsung)
{
RDCLOG("AMD or Samsung 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();
}
#if DISABLED(RDOC_ANDROID) && DISABLED(RDOC_APPLE)
else if(m_DriverInfo.vendor == GPUVendor::nVidia)
{
RDCLOG("NVIDIA GPU detected - trying to initialise NVIDIA counters");
countersNV = new NVGLCounters();
}
#endif
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;
}
#if DISABLED(RDOC_ANDROID) && DISABLED(RDOC_APPLE)
if(countersNV && countersNV->Init(m_pDriver))
{
m_pNVCounters = countersNV;
}
else
{
delete countersNV;
m_pNVCounters = NULL;
}
#endif
}
}
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;
}
WrappedOpenGL::BufferData &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);
WrappedOpenGL::TextureData &res = m_pDriver->m_Textures[id];
WrappedOpenGL &drv = *m_pDriver;
tex.resourceId = 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);
WrappedOpenGL::BufferData &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 = 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(ResourceIDGen::IsReplayOnlyID(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)
{
WrappedOpenGL::TextureData &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) && ResourceIDGen::IsReplayOnlyID(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->HasShader(shader))
return {};
const WrappedOpenGL::ShaderData &shaderDetails = m_pDriver->GetShader(shader);
if(shaderDetails.GetReflection()->resourceId == ResourceId())
{
RDCERR("Can't get shader details without successful reflect");
return {};
}
return {{shaderDetails.GetReflection()->entryPoint, shaderDetails.GetReflection()->stage}};
}
const ShaderReflection *GLReplay::GetShader(ResourceId pipeline, ResourceId shader,
ShaderEntryPoint entry)
{
const WrappedOpenGL::ShaderData &shaderDetails = m_pDriver->GetShader(shader);
if(shaderDetails.GetReflection()->resourceId == ResourceId())
{
RDCERR("Can't get shader details without successful reflect");
return NULL;
}
return shaderDetails.GetReflection();
}
rdcarray<rdcstr> GLReplay::GetDisassemblyTargets(bool withPipeline)
{
return {SPIRVDisassemblyTarget};
}
rdcstr GLReplay::DisassembleShader(ResourceId pipeline, const ShaderReflection *refl,
const rdcstr &target)
{
const WrappedOpenGL::ShaderData &shaderDetails = m_pDriver->GetShader(refl->resourceId);
if(shaderDetails.sources.empty() && shaderDetails.spirvWords.empty() &&
shaderDetails.convertedSpirvWords.empty())
return "; Invalid Shader Specified";
if(target == SPIRVDisassemblyTarget || target.empty())
{
m_pDriver->GetWriteableShader(refl->resourceId).Disassemble(refl->entryPoint);
return shaderDetails.disassembly;
}
return StringFormat::Fmt("; Invalid disassembly target %s", target.c_str());
}
void GLReplay::SavePipelineState(uint32_t eventId)
{
if(!m_GLPipelineState)
return;
GLPipe::State &pipe = *m_GLPipelineState;
WrappedOpenGL &drv = *m_pDriver;
GLResourceManager *rm = m_pDriver->GetResourceManager();
MakeCurrentReplayContext(&m_ReplayCtx);
GLRenderState rs;
rs.FetchState(&drv);
// Index buffer
ContextPair &ctx = drv.GetCtx();
pipe.descriptorStore = m_pDriver->m_DescriptorsID;
pipe.descriptorCount = EncodeGLDescriptorIndex({GLDescriptorMapping::Count, 0});
pipe.descriptorByteSize = 1;
m_Access.clear();
GLuint vao = 0;
drv.glGetIntegerv(eGL_VERTEX_ARRAY_BINDING, (GLint *)&vao);
pipe.vertexInput.vertexArrayObject = rm->GetResID(VertexArrayRes(ctx, vao));
GLuint ibuffer = 0;
drv.glGetIntegerv(eGL_ELEMENT_ARRAY_BUFFER_BINDING, (GLint *)&ibuffer);
pipe.vertexInput.indexBuffer = 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->GetDrawParameters(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->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::SInt;
break;
case eGL_UNSIGNED_INT_2_10_10_10_REV:
fmt.type = ResourceFormatType::R10G10B10A2;
fmt.compCount = 4;
fmt.compType = CompType::UInt;
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);
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;
GLPipe::Shader *stages[NumShaderStages] = {
&pipe.vertexShader, &pipe.tessControlShader, &pipe.tessEvalShader,
&pipe.geometryShader, &pipe.fragmentShader, &pipe.computeShader,
};
const ShaderReflection *refls[NumShaderStages] = {NULL};
ResourceId progIds[NumShaderStages];
ResourceId shadIds[NumShaderStages];
GLuint progForStage[NumShaderStages] = {};
bool spirv[NumShaderStages] = {false};
for(size_t i = 0; i < NumShaderStages; i++)
{
if(!stages[i])
continue;
stages[i]->programResourceId = stages[i]->shaderResourceId = ResourceId();
stages[i]->reflection = NULL;
}
rdcarray<int32_t> vertexAttrBindings;
{
GLuint curProg = 0;
drv.glGetIntegerv(eGL_CURRENT_PROGRAM, (GLint *)&curProg);
if(curProg == 0)
{
GLuint curPipe = 0;
drv.glGetIntegerv(eGL_PROGRAM_PIPELINE_BINDING, (GLint *)&curPipe);
if(curPipe != 0)
{
ResourceId id = rm->GetResID(ProgramPipeRes(ctx, curPipe));
const WrappedOpenGL::PipelineData &pipeDetails = m_pDriver->GetPipeline(id);
pipe.pipelineResourceId = rm->GetUnreplacedID(id);
for(size_t i = 0; i < ARRAY_COUNT(pipeDetails.stageShaders); i++)
{
if(!stages[i])
continue;
if(pipeDetails.stageShaders[i] != ResourceId())
{
progIds[i] = pipeDetails.stagePrograms[i];
shadIds[i] = pipeDetails.stageShaders[i];
progForStage[i] = rm->GetResource(pipeDetails.stagePrograms[i]).name;
}
}
}
}
else
{
ResourceId id = rm->GetResID(ProgramRes(ctx, curProg));
const WrappedOpenGL::ProgramData &progDetails = m_pDriver->GetProgram(id);
pipe.pipelineResourceId = ResourceId();
for(size_t i = 0; i < ARRAY_COUNT(progDetails.stageShaders); i++)
{
if(!stages[i])
continue;
if(progDetails.stageShaders[i] != ResourceId())
{
progIds[i] = id;
shadIds[i] = progDetails.stageShaders[i];
progForStage[i] = curProg;
}
}
}
}
for(size_t i = 0; i < NumShaderStages; i++)
{
if(progForStage[i])
{
progForStage[i] = rm->GetResource(progIds[i]).name;
stages[i]->programResourceId = rm->GetUnreplacedID(progIds[i]);
stages[i]->shaderResourceId = rm->GetUnreplacedID(shadIds[i]);
const WrappedOpenGL::ShaderData &shaderDetails = m_pDriver->GetShader(shadIds[i]);
if(shaderDetails.GetReflection()->resourceId == ResourceId())
stages[i]->reflection = refls[i] = NULL;
else
stages[i]->reflection = refls[i] = shaderDetails.GetReflection();
if(!shaderDetails.spirvWords.empty())
spirv[i] = true;
if(i == 0)
EvaluateVertexAttributeBinds(progForStage[i], refls[i], spirv[i], vertexAttrBindings);
}
else if(stages[i])
{
stages[i]->programResourceId = stages[i]->shaderResourceId = ResourceId();
stages[i]->reflection = NULL;
}
}
for(size_t i = 0; i < pipe.vertexInput.attributes.size(); i++)
{
if(i < vertexAttrBindings.size())
pipe.vertexInput.attributes[i].boundShaderInput = vertexAttrBindings[i];
else
pipe.vertexInput.attributes[i].boundShaderInput = -1;
}
pipe.textureCompleteness.clear();
for(size_t s = 0; s < NumShaderStages; s++)
{
const ShaderReflection *refl = refls[s];
if(!refl)
continue;
GLuint prog = progForStage[s];
DescriptorAccess access;
access.descriptorStore = m_pDriver->m_DescriptorsID;
access.stage = refl->stage;
access.byteSize = 1;
m_Access.reserve(m_Access.size() + refl->constantBlocks.size() +
refl->readOnlyResources.size() + refl->readWriteResources.size());
RDCASSERT(refl->constantBlocks.size() < 0xffff, refl->constantBlocks.size());
for(uint16_t i = 0; i < refl->constantBlocks.size(); i++)
{
uint32_t slot = 0;
bool used = false;
GetCurrentBinding(prog, refl, refl->constantBlocks[i], slot, used);
access.staticallyUnused = !used;
access.type = DescriptorType::ConstantBuffer;
access.index = i;
if(!refl->constantBlocks[i].bufferBacked)
access.byteOffset = EncodeGLDescriptorIndex({GLDescriptorMapping::BareUniforms, (uint32_t)s});
else
access.byteOffset =
EncodeGLDescriptorIndex({GLDescriptorMapping::UniformBinding, (uint32_t)slot});
m_Access.push_back(access);
}
RDCASSERT(refl->readOnlyResources.size() < 0xffff, refl->readOnlyResources.size());
for(uint16_t i = 0; i < refl->readOnlyResources.size(); i++)
{
uint32_t slot = 0;
bool used = false;
GetCurrentBinding(prog, refl, refl->readOnlyResources[i], slot, used);
access.staticallyUnused = !used;
GLDescriptorMapping descType = GLDescriptorMapping::Tex2D;
switch(refl->readOnlyResources[i].textureType)
{
case TextureType::Buffer: descType = GLDescriptorMapping::TexBuffer; break;
case TextureType::Texture1D: descType = GLDescriptorMapping::Tex1D; break;
case TextureType::Texture1DArray: descType = GLDescriptorMapping::Tex1DArray; break;
case TextureType::Texture2D: descType = GLDescriptorMapping::Tex2D; break;
case TextureType::TextureRect: descType = GLDescriptorMapping::TexRect; break;
case TextureType::Texture2DArray: descType = GLDescriptorMapping::Tex2DArray; break;
case TextureType::Texture2DMS: descType = GLDescriptorMapping::Tex2DMS; break;
case TextureType::Texture2DMSArray: descType = GLDescriptorMapping::Tex2DMSArray; break;
case TextureType::Texture3D: descType = GLDescriptorMapping::Tex3D; break;
case TextureType::TextureCube: descType = GLDescriptorMapping::TexCube; break;
case TextureType::TextureCubeArray: descType = GLDescriptorMapping::TexCubeArray; break;
case TextureType::Unknown:
case TextureType::Count:
RDCERR("Invalid resource type on binding %s", refl->readOnlyResources[i].name.c_str());
break;
}
access.type = DescriptorType::ImageSampler;
if(descType == GLDescriptorMapping::TexBuffer)
access.type = DescriptorType::TypedBuffer;
access.index = i;
access.byteOffset = EncodeGLDescriptorIndex({descType, slot});
m_Access.push_back(access);
// 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
drv.glActiveTexture(GLenum(eGL_TEXTURE0 + slot));
GLenum binding = eGL_NONE;
switch(refl->readOnlyResources[i].textureType)
{
case TextureType::Unknown: binding = eGL_NONE; break;
case TextureType::Buffer: binding = eGL_TEXTURE_BINDING_BUFFER; break;
case TextureType::Texture1D: binding = eGL_TEXTURE_BINDING_1D; break;
case TextureType::Texture1DArray: binding = eGL_TEXTURE_BINDING_1D_ARRAY; break;
case TextureType::Texture2D: binding = eGL_TEXTURE_BINDING_2D; break;
case TextureType::TextureRect: binding = eGL_TEXTURE_BINDING_RECTANGLE; break;
case TextureType::Texture2DArray: binding = eGL_TEXTURE_BINDING_2D_ARRAY; break;
case TextureType::Texture2DMS: binding = eGL_TEXTURE_BINDING_2D_MULTISAMPLE; break;
case TextureType::Texture2DMSArray:
binding = eGL_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY;
break;
case TextureType::Texture3D: binding = eGL_TEXTURE_BINDING_3D; break;
case TextureType::TextureCube: binding = eGL_TEXTURE_BINDING_CUBE_MAP; break;
case TextureType::TextureCubeArray: binding = eGL_TEXTURE_BINDING_CUBE_MAP_ARRAY; break;
case TextureType::Count: RDCERR("Invalid shader resource type"); break;
}
GLuint tex = 0;
if(descType == GLDescriptorMapping::TexCubeArray && !HasExt[ARB_texture_cube_map_array])
tex = 0;
else
drv.glGetIntegerv(binding, (GLint *)&tex);
GLuint samp = 0;
if(HasExt[ARB_sampler_objects])
drv.glGetIntegerv(eGL_SAMPLER_BINDING, (GLint *)&samp);
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(TextureTarget(binding), tex, samp)));
if(!it->second.empty())
{
GLPipe::TextureCompleteness completeness;
completeness.descriptorByteOffset = access.byteOffset;
completeness.completeStatus = it->second;
pipe.textureCompleteness.push_back(completeness);
}
}
RDCASSERT(refl->readWriteResources.size() < 0xffff, refl->readWriteResources.size());
for(uint16_t i = 0; i < refl->readWriteResources.size(); i++)
{
uint32_t slot = 0;
bool used = false;
GetCurrentBinding(prog, refl, refl->readWriteResources[i], slot, used);
access.staticallyUnused = !used;
GLDescriptorMapping descType = GLDescriptorMapping::Images;
if(refl->readWriteResources[i].isTexture)
{
access.type = DescriptorType::ReadWriteImage;
if(refl->readWriteResources[i].textureType == TextureType::Buffer)
access.type = DescriptorType::ReadWriteTypedBuffer;
}
else
{
access.type = DescriptorType::ReadWriteBuffer;
descType = GLDescriptorMapping::ShaderStorage;
if(refl->readWriteResources[i].variableType.rows == 1 &&
refl->readWriteResources[i].variableType.columns == 1 &&
refl->readWriteResources[i].variableType.baseType == VarType::UInt)
{
descType = GLDescriptorMapping::AtomicCounter;
}
}
access.index = i;
access.byteOffset = EncodeGLDescriptorIndex({descType, slot});
m_Access.push_back(access);
}
}
// 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 check if two uniforms with different types point to the same binding
for(uint32_t unit = 0; unit < (uint32_t)numTexUnits; unit++)
{
rdcstr typeConflict;
GLenum binding = eGL_NONE;
GLenum target = eGL_NONE;
TextureType resType = TextureType::Unknown;
rdcstr firstBindName;
rdcarray<uint32_t> descriptorsReferenced;
for(const DescriptorAccess &access : m_Access)
{
// only look at read-only descriptors, these are the texture units that can clash
if(!IsReadOnlyDescriptor(access.type))
continue;
const ShaderReflection *refl = refls[(uint32_t)access.stage];
if(refl == NULL)
{
RDCERR("Unexpected NULL reflection on %s shader with a descriptor access",
ToStr(access.stage).c_str());
continue;
}
uint32_t accessedUnit = DecodeGLDescriptorIndex(access.byteOffset).idx;
// accessed the same unit, check its binding
if(accessedUnit == unit)
{
if(!descriptorsReferenced.contains(access.byteOffset))
descriptorsReferenced.push_back(access.byteOffset);
const ShaderResource &res = refl->readOnlyResources[access.index];
GLenum t = eGL_NONE;
switch(res.textureType)
{
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);
if(binding == eGL_NONE)
{
binding = t;
firstBindName = res.name;
resType = res.textureType;
}
else if(binding == t)
{
// two uniforms with the same type pointing to the same slot is fine
binding = t;
}
else if(binding != t)
{
RDCERR("Two uniforms pointing to texture unit %d with types %s and %s", unit,
ToStr(binding).c_str(), ToStr(t).c_str());
if(typeConflict.empty())
{
typeConflict = StringFormat::Fmt("First binding found '%s' is %s",
firstBindName.c_str(), ToStr(resType).c_str());
}
typeConflict +=
StringFormat::Fmt(", '%s' is %s", res.name.c_str(), ToStr(res.textureType).c_str());
}
}
}
// if we found a type conflict, add an entry for all descriptors
if(!typeConflict.empty())
{
for(uint32_t descriptor : descriptorsReferenced)
{
bool found = false;
for(GLPipe::TextureCompleteness &completeness : pipe.textureCompleteness)
{
if(completeness.descriptorByteOffset == descriptor)
{
// don't worry about overwriting, the descriptor byte offset is unique to the unit so
// we should only set this at most once
completeness.typeConflict = typeConflict;
found = true;
}
}
if(!found)
{
GLPipe::TextureCompleteness completeness;
completeness.descriptorByteOffset = descriptor;
completeness.typeConflict = typeConflict;
pipe.textureCompleteness.push_back(completeness);
}
}
}
}
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->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->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(size_t i = 0; i < ARRAY_COUNT(rs.Subroutines); 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));
}
}
drv.glActiveTexture(activeTexture);
// 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->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].resource = id;
if(id != ResourceId())
pipe.framebuffer.drawFBO.colorAttachments[i].format = GetTexture(id).format;
GLenum attachment = GLenum(eGL_COLOR_ATTACHMENT0 + i);
if(pipe.framebuffer.drawFBO.colorAttachments[i].resource != ResourceId() && !rbCol[i])
GetFramebufferMipAndLayer(curDrawFBO, attachment,
(GLint *)&pipe.framebuffer.drawFBO.colorAttachments[i].firstMip,
(GLint *)&pipe.framebuffer.drawFBO.colorAttachments[i].firstSlice);
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 & 0xffff;
}
}
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 & 0xffff;
pipe.framebuffer.drawFBO.colorAttachments[i].firstSlice = startView & 0xffff;
}
}
}
}
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]);
}
ResourceId id =
rm->GetResID(rbDepth ? RenderbufferRes(ctx, curDepth) : TextureRes(ctx, curDepth));
pipe.framebuffer.drawFBO.depthAttachment.resource = id;
pipe.framebuffer.drawFBO.stencilAttachment.resource =
rm->GetResID(rbStencil ? RenderbufferRes(ctx, curStencil) : TextureRes(ctx, curStencil));
if(pipe.framebuffer.drawFBO.depthAttachment.resource != ResourceId() && !rbDepth)
GetFramebufferMipAndLayer(curDrawFBO, eGL_DEPTH_ATTACHMENT,
&pipe.framebuffer.drawFBO.depthAttachment.firstMip,
&pipe.framebuffer.drawFBO.depthAttachment.firstSlice);
if(pipe.framebuffer.drawFBO.stencilAttachment.resource != ResourceId() && !rbStencil)
GetFramebufferMipAndLayer(curDrawFBO, eGL_STENCIL_ATTACHMENT,
&pipe.framebuffer.drawFBO.stencilAttachment.firstMip,
&pipe.framebuffer.drawFBO.stencilAttachment.firstSlice);
pipe.framebuffer.drawFBO.depthAttachment.numSlices = 1;
pipe.framebuffer.drawFBO.stencilAttachment.numSlices = 1;
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 & 0xffff;
}
}
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 & 0xffff;
pipe.framebuffer.drawFBO.depthAttachment.firstSlice = startView & 0xffff;
}
}
}
if(pipe.framebuffer.drawFBO.stencilAttachment.resource ==
pipe.framebuffer.drawFBO.depthAttachment.resource)
{
pipe.framebuffer.drawFBO.stencilAttachment.firstSlice =
pipe.framebuffer.drawFBO.depthAttachment.firstSlice;
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->GetResID(FramebufferRes(ctx, curReadFBO));
pipe.framebuffer.readFBO.colorAttachments.resize(numCols);
for(GLint i = 0; i < numCols; i++)
{
pipe.framebuffer.readFBO.colorAttachments[i].resource =
rm->GetResID(rbCol[i] ? RenderbufferRes(ctx, curCol[i]) : TextureRes(ctx, curCol[i]));
if(pipe.framebuffer.readFBO.colorAttachments[i].resource != ResourceId() && !rbCol[i])
GetFramebufferMipAndLayer(curReadFBO, GLenum(eGL_COLOR_ATTACHMENT0 + i),
&pipe.framebuffer.readFBO.colorAttachments[i].firstMip,
&pipe.framebuffer.readFBO.colorAttachments[i].firstSlice);
}
pipe.framebuffer.readFBO.depthAttachment.resource =
rm->GetResID(rbDepth ? RenderbufferRes(ctx, curDepth) : TextureRes(ctx, curDepth));
pipe.framebuffer.readFBO.stencilAttachment.resource =
rm->GetResID(rbStencil ? RenderbufferRes(ctx, curStencil) : TextureRes(ctx, curStencil));
if(pipe.framebuffer.readFBO.depthAttachment.resource != ResourceId() && !rbDepth)
GetFramebufferMipAndLayer(curReadFBO, eGL_DEPTH_ATTACHMENT,
&pipe.framebuffer.readFBO.depthAttachment.firstMip,
&pipe.framebuffer.readFBO.depthAttachment.firstSlice);
if(pipe.framebuffer.readFBO.stencilAttachment.resource != ResourceId() && !rbStencil)
GetFramebufferMipAndLayer(curReadFBO, eGL_STENCIL_ATTACHMENT,
&pipe.framebuffer.readFBO.stencilAttachment.firstMip,
&pipe.framebuffer.readFBO.stencilAttachment.firstSlice);
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];
}
rdcarray<Descriptor> GLReplay::GetDescriptors(ResourceId descriptorStore,
const rdcarray<DescriptorRange> &ranges)
{
rdcarray<Descriptor> ret;
if(descriptorStore != m_pDriver->m_DescriptorsID)
{
RDCERR("Descriptors query for invalid descriptor store on fixed bindings API (OpenGL)");
return ret;
}
MakeCurrentReplayContext(&m_ReplayCtx);
WrappedOpenGL &drv = *m_pDriver;
GLResourceManager *rm = m_pDriver->GetResourceManager();
ContextPair &ctx = drv.GetCtx();
GLRenderState rs;
rs.FetchState(&drv);
size_t count = 0;
for(const DescriptorRange &r : ranges)
count += r.count;
ret.resize(count);
size_t dst = 0;
for(const DescriptorRange &r : ranges)
{
uint32_t descriptorId = r.offset;
for(uint32_t i = 0; i < r.count; i++, dst++, descriptorId++)
{
GLDescriptorLocation idx = DecodeGLDescriptorIndex(descriptorId);
if(idx.type == GLDescriptorMapping::Invalid)
{
}
else if(idx.type == GLDescriptorMapping::BareUniforms)
{
// not feasible to emulate backing store for bare uniforms. We could synthesise our own byte
// layout and query uniform values into a buffer but... let's not.
ret[dst].type = DescriptorType::ConstantBuffer;
ret[dst].byteSize = 16 * 1024;
}
else if(idx.type == GLDescriptorMapping::UniformBinding)
{
ret[dst].type = DescriptorType::ConstantBuffer;
if(rs.UniformBinding[idx.idx].res.name != 0)
{
ResourceId id = rm->GetResID(rs.UniformBinding[idx.idx].res);
ret[dst].resource = id;
ret[dst].byteOffset = rs.UniformBinding[idx.idx].start;
ret[dst].byteSize = rs.UniformBinding[idx.idx].size;
if(ret[dst].byteSize == 0)
ret[dst].byteSize = m_pDriver->m_Buffers[id].size;
}
}
else if(idx.type == GLDescriptorMapping::AtomicCounter)
{
ret[dst].type = DescriptorType::ReadWriteBuffer;
if(rs.AtomicCounter[idx.idx].res.name != 0)
{
ResourceId id = rm->GetResID(rs.AtomicCounter[idx.idx].res);
ret[dst].resource = id;
ret[dst].byteOffset = rs.AtomicCounter[idx.idx].start;
ret[dst].byteSize = rs.AtomicCounter[idx.idx].size;
if(ret[dst].byteSize == 0)
ret[dst].byteSize = m_pDriver->m_Buffers[id].size;
}
}
else if(idx.type == GLDescriptorMapping::ShaderStorage)
{
ret[dst].type = DescriptorType::ReadWriteBuffer;
if(rs.ShaderStorage[idx.idx].res.name != 0)
{
ResourceId id = rm->GetResID(rs.ShaderStorage[idx.idx].res);
ret[dst].resource = id;
ret[dst].byteOffset = rs.ShaderStorage[idx.idx].start;
ret[dst].byteSize = rs.ShaderStorage[idx.idx].size;
if(ret[dst].byteSize == 0)
ret[dst].byteSize = m_pDriver->m_Buffers[id].size;
}
}
else if(idx.type == GLDescriptorMapping::Images)
{
ret[dst].type = DescriptorType::ReadWriteImage;
if(rs.Images[idx.idx].res.name != 0)
{
ResourceId id = rm->GetResID(rs.Images[idx.idx].res);
ret[dst].resource = id;
ret[dst].firstMip = rs.Images[idx.idx].level & 0xff;
ret[dst].numMips = 1;
ret[dst].firstSlice = rs.Images[idx.idx].layer & 0xffff;
if(rs.Images[idx.idx].access == eGL_READ_ONLY)
{
ret[dst].flags = DescriptorFlags::ReadOnlyAccess;
}
else if(rs.Images[idx.idx].access == eGL_WRITE_ONLY)
{
ret[dst].flags = DescriptorFlags::WriteOnlyAccess;
}
ret[dst].format = MakeResourceFormat(eGL_TEXTURE_2D, rs.Images[idx.idx].format);
CacheTexture(id);
ret[dst].textureType = m_CachedTextures[id].type;
if(ret[dst].textureType == TextureType::Texture3D)
ret[dst].numSlices = rs.Images[idx.idx].layered
? (m_CachedTextures[id].depth >> ret[dst].firstMip) & 0xffff
: 1;
else
ret[dst].numSlices =
rs.Images[idx.idx].layered ? m_CachedTextures[id].arraysize & 0xffff : 1;
if(ret[dst].textureType == TextureType::Buffer)
ret[dst].type = DescriptorType::ReadWriteTypedBuffer;
}
}
else
{
ret[dst].type = DescriptorType::ImageSampler;
if(idx.type == GLDescriptorMapping::TexBuffer)
ret[dst].type = DescriptorType::TypedBuffer;
drv.glActiveTexture(GLenum(eGL_TEXTURE0 + idx.idx));
GLuint tex = 0;
GLenum target = eGL_NONE;
if(idx.type == GLDescriptorMapping::TexCubeArray && !HasExt[ARB_texture_cube_map_array])
{
tex = 0;
target = eGL_TEXTURE_CUBE_MAP_ARRAY;
}
else
{
switch(idx.type)
{
case GLDescriptorMapping::Tex1D:
target = eGL_TEXTURE_1D;
ret[dst].textureType = TextureType::Texture1D;
if(IsGLES)
continue;
break;
case GLDescriptorMapping::Tex2D:
target = eGL_TEXTURE_2D;
ret[dst].textureType = TextureType::Texture2D;
break;
case GLDescriptorMapping::Tex3D:
target = eGL_TEXTURE_3D;
ret[dst].textureType = TextureType::Texture3D;
break;
case GLDescriptorMapping::Tex1DArray:
target = eGL_TEXTURE_1D_ARRAY;
ret[dst].textureType = TextureType::Texture1DArray;
if(IsGLES)
continue;
break;
case GLDescriptorMapping::Tex2DArray:
target = eGL_TEXTURE_2D_ARRAY;
ret[dst].textureType = TextureType::Texture2DArray;
break;
case GLDescriptorMapping::TexCubeArray:
target = eGL_TEXTURE_CUBE_MAP_ARRAY;
ret[dst].textureType = TextureType::TextureCubeArray;
if(!HasExt[ARB_texture_cube_map_array])
continue;
break;
case GLDescriptorMapping::TexRect:
target = eGL_TEXTURE_RECTANGLE;
ret[dst].textureType = TextureType::TextureRect;
if(IsGLES)
continue;
break;
case GLDescriptorMapping::TexBuffer:
target = eGL_TEXTURE_BUFFER;
ret[dst].textureType = TextureType::Buffer;
if(!HasExt[ARB_texture_buffer_object])
continue;
break;
case GLDescriptorMapping::TexCube:
target = eGL_TEXTURE_CUBE_MAP;
ret[dst].textureType = TextureType::TextureCube;
break;
case GLDescriptorMapping::Tex2DMS:
target = eGL_TEXTURE_2D_MULTISAMPLE;
ret[dst].textureType = TextureType::Texture2DMS;
if(!HasExt[ARB_texture_multisample_no_array] && !HasExt[ARB_texture_multisample])
continue;
break;
case GLDescriptorMapping::Tex2DMSArray:
target = eGL_TEXTURE_2D_MULTISAMPLE_ARRAY;
ret[dst].textureType = TextureType::Texture2DMSArray;
if(!HasExt[ARB_texture_multisample])
continue;
break;
case GLDescriptorMapping::AtomicCounter:
case GLDescriptorMapping::ShaderStorage:
case GLDescriptorMapping::BareUniforms:
case GLDescriptorMapping::UniformBinding:
case GLDescriptorMapping::Images:
case GLDescriptorMapping::Invalid:
case GLDescriptorMapping::Count: target = eGL_NONE; break;
}
}
if(target == eGL_NONE)
continue;
GLenum binding = TextureBinding(target);
drv.glGetIntegerv(binding, (GLint *)&tex);
if(tex == 0)
continue;
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;
}
ResourceId id = rm->GetResID(TextureRes(ctx, tex));
ret[dst].resource = id;
ret[dst].firstMip = firstMip & 0xff;
ret[dst].numMips = numMips & 0xff;
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);
ret[dst].format = MakeResourceFormat(target, fmt);
if(IsDepthStencilFormat(fmt))
{
GLint depthMode = eGL_DEPTH_COMPONENT;
if(HasExt[ARB_stencil_texturing])
drv.glGetTextureParameterivEXT(tex, target, eGL_DEPTH_STENCIL_TEXTURE_MODE, &depthMode);
ret[dst].format = ResourceFormat();
ret[dst].format.type = ResourceFormatType::Regular;
ret[dst].format.compByteWidth = 1;
ret[dst].format.compCount = 1;
if(depthMode == eGL_DEPTH_COMPONENT)
ret[dst].format.compType = CompType::Depth;
else if(depthMode == eGL_STENCIL_INDEX)
ret[dst].format.compType = CompType::UInt;
}
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);
ret[dst].swizzle.red = MakeSwizzle(swizzles[0]);
ret[dst].swizzle.green = MakeSwizzle(swizzles[1]);
ret[dst].swizzle.blue = MakeSwizzle(swizzles[2]);
ret[dst].swizzle.alpha = MakeSwizzle(swizzles[3]);
GLuint samp = 0;
if(HasExt[ARB_sampler_objects])
drv.glGetIntegerv(eGL_SAMPLER_BINDING, (GLint *)&samp);
ret[dst].secondary = rm->GetResID(SamplerRes(ctx, samp));
}
}
}
return ret;
}
rdcarray<SamplerDescriptor> GLReplay::GetSamplerDescriptors(ResourceId descriptorStore,
const rdcarray<DescriptorRange> &ranges)
{
rdcarray<SamplerDescriptor> ret;
if(descriptorStore != m_pDriver->m_DescriptorsID)
{
RDCERR("Descriptors query for invalid descriptor store on fixed bindings API (OpenGL)");
return ret;
}
MakeCurrentReplayContext(&m_ReplayCtx);
WrappedOpenGL &drv = *m_pDriver;
GLResourceManager *rm = m_pDriver->GetResourceManager();
ContextPair &ctx = drv.GetCtx();
size_t count = 0;
for(const DescriptorRange &r : ranges)
count += r.count;
ret.resize(count);
size_t dst = 0;
for(const DescriptorRange &r : ranges)
{
uint32_t descriptorId = r.offset;
for(uint32_t i = 0; i < r.count; i++, dst++, descriptorId++)
{
GLDescriptorLocation idx = DecodeGLDescriptorIndex(descriptorId);
if(idx.type == GLDescriptorMapping::Invalid || idx.type == GLDescriptorMapping::Images ||
idx.type == GLDescriptorMapping::AtomicCounter ||
idx.type == GLDescriptorMapping::ShaderStorage ||
idx.type == GLDescriptorMapping::UniformBinding)
continue;
drv.glActiveTexture(GLenum(eGL_TEXTURE0 + idx.idx));
GLuint tex = 0;
GLenum target = eGL_NONE;
if(idx.type == GLDescriptorMapping::TexCubeArray && !HasExt[ARB_texture_cube_map_array])
{
tex = 0;
target = eGL_TEXTURE_CUBE_MAP_ARRAY;
}
else
{
switch(idx.type)
{
case GLDescriptorMapping::Tex1D:
target = eGL_TEXTURE_1D;
if(IsGLES)
continue;
break;
case GLDescriptorMapping::Tex2D: target = eGL_TEXTURE_2D; break;
case GLDescriptorMapping::Tex3D: target = eGL_TEXTURE_3D; break;
case GLDescriptorMapping::Tex1DArray:
target = eGL_TEXTURE_1D_ARRAY;
if(IsGLES)
continue;
break;
case GLDescriptorMapping::Tex2DArray: target = eGL_TEXTURE_2D_ARRAY; break;
case GLDescriptorMapping::TexCubeArray:
target = eGL_TEXTURE_CUBE_MAP_ARRAY;
if(!HasExt[ARB_texture_cube_map_array])
continue;
break;
case GLDescriptorMapping::TexRect:
target = eGL_TEXTURE_RECTANGLE;
if(IsGLES)
continue;
break;
case GLDescriptorMapping::TexBuffer:
target = eGL_TEXTURE_BUFFER;
if(!HasExt[ARB_texture_buffer_object])
continue;
break;
case GLDescriptorMapping::TexCube: target = eGL_TEXTURE_CUBE_MAP; break;
case GLDescriptorMapping::Tex2DMS:
target = eGL_TEXTURE_2D_MULTISAMPLE;
if(!HasExt[ARB_texture_multisample_no_array] && !HasExt[ARB_texture_multisample])
continue;
break;
case GLDescriptorMapping::Tex2DMSArray:
target = eGL_TEXTURE_2D_MULTISAMPLE_ARRAY;
if(!HasExt[ARB_texture_multisample])
continue;
break;
case GLDescriptorMapping::AtomicCounter:
case GLDescriptorMapping::ShaderStorage:
case GLDescriptorMapping::BareUniforms:
case GLDescriptorMapping::UniformBinding:
case GLDescriptorMapping::Images:
case GLDescriptorMapping::Invalid:
case GLDescriptorMapping::Count: target = eGL_NONE; break;
}
}
if(target == eGL_NONE)
continue;
GLenum binding = TextureBinding(target);
GLuint samp = 0;
if(HasExt[ARB_sampler_objects])
drv.glGetIntegerv(eGL_SAMPLER_BINDING, (GLint *)&samp);
drv.glGetIntegerv(binding, (GLint *)&tex);
if(samp == 0 && tex == 0)
continue;
ret[dst].object = rm->GetResID(SamplerRes(ctx, samp));
// GL has separate sampler objects but they don't exist as separate sampler descriptors
ret[dst].type = DescriptorType::ImageSampler;
if(target != eGL_TEXTURE_2D_MULTISAMPLE && target != eGL_TEXTURE_2D_MULTISAMPLE_ARRAY)
{
if(samp != 0)
drv.glGetSamplerParameterfv(samp, eGL_TEXTURE_BORDER_COLOR,
ret[dst].borderColorValue.floatValue.data());
else
drv.glGetTextureParameterfvEXT(tex, target, eGL_TEXTURE_BORDER_COLOR,
ret[dst].borderColorValue.floatValue.data());
ret[dst].borderColorType = CompType::Float;
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);
ret[dst].addressU = 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);
ret[dst].addressV = 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);
ret[dst].addressW = MakeAddressMode((GLenum)v);
// GLES 3 is always seamless
if(IsGLES && GLCoreVersion > 30)
{
ret[dst].seamlessCubemaps = true;
}
else if(!IsGLES)
{
// on GLES 2 this is always going to be false, GL has a toggle
ret[dst].seamlessCubemaps = drv.glIsEnabled(eGL_TEXTURE_CUBE_MAP_SEAMLESS) != GL_FALSE;
}
v = 0;
if(samp != 0)
drv.glGetSamplerParameteriv(samp, eGL_TEXTURE_COMPARE_FUNC, &v);
else
drv.glGetTextureParameterivEXT(tex, target, eGL_TEXTURE_COMPARE_FUNC, &v);
ret[dst].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, &ret[dst].maxAnisotropy);
else
drv.glGetTextureParameterfvEXT(tex, target, eGL_TEXTURE_MAX_ANISOTROPY,
&ret[dst].maxAnisotropy);
}
else
{
ret[dst].maxAnisotropy = 0.0f;
}
ret[dst].filter = MakeFilter((GLenum)minf, (GLenum)magf, ret[dst].maxAnisotropy);
v = 0;
if(samp != 0)
drv.glGetSamplerParameteriv(samp, eGL_TEXTURE_COMPARE_MODE, &v);
else
drv.glGetTextureParameterivEXT(tex, target, eGL_TEXTURE_COMPARE_MODE, &v);
ret[dst].filter.filter = (GLenum)v == eGL_COMPARE_REF_TO_TEXTURE ? FilterFunction::Comparison
: FilterFunction::Normal;
if(samp != 0)
drv.glGetSamplerParameterfv(samp, eGL_TEXTURE_MAX_LOD, &ret[dst].maxLOD);
else
drv.glGetTextureParameterfvEXT(tex, target, eGL_TEXTURE_MAX_LOD, &ret[dst].maxLOD);
if(samp != 0)
drv.glGetSamplerParameterfv(samp, eGL_TEXTURE_MIN_LOD, &ret[dst].minLOD);
else
drv.glGetTextureParameterfvEXT(tex, target, eGL_TEXTURE_MIN_LOD, &ret[dst].minLOD);
if(!IsGLES)
{
if(samp != 0)
drv.glGetSamplerParameterfv(samp, eGL_TEXTURE_LOD_BIAS, &ret[dst].mipBias);
else
drv.glGetTextureParameterfvEXT(tex, target, eGL_TEXTURE_LOD_BIAS, &ret[dst].mipBias);
}
}
}
}
return ret;
}
rdcarray<DescriptorAccess> GLReplay::GetDescriptorAccess(uint32_t eventId)
{
return m_Access;
}
rdcarray<DescriptorLogicalLocation> GLReplay::GetDescriptorLocations(
ResourceId descriptorStore, const rdcarray<DescriptorRange> &ranges)
{
rdcarray<DescriptorLogicalLocation> ret;
if(descriptorStore != m_pDriver->m_DescriptorsID)
{
RDCERR("Descriptors query for invalid descriptor store on fixed bindings API (OpenGL)");
return ret;
}
size_t count = 0;
for(const DescriptorRange &r : ranges)
count += r.count;
ret.resize(count);
size_t dst = 0;
for(const DescriptorRange &r : ranges)
{
uint32_t descriptorByteOffset = r.offset;
for(uint32_t i = 0; i < r.count; i++, dst++, descriptorByteOffset++)
{
DescriptorLogicalLocation &dstLoc = ret[dst];
GLDescriptorLocation srcLoc = DecodeGLDescriptorIndex(descriptorByteOffset);
const char *prefix = "Unknown";
dstLoc.stageMask = ShaderStageMask::All;
switch(srcLoc.type)
{
case GLDescriptorMapping::BareUniforms:
prefix = "Uniforms";
dstLoc.category = DescriptorCategory::ConstantBlock;
break;
case GLDescriptorMapping::UniformBinding:
prefix = "UBO";
dstLoc.category = DescriptorCategory::ConstantBlock;
break;
case GLDescriptorMapping::Tex1D:
prefix = "Tex1D";
dstLoc.category = DescriptorCategory::ReadOnlyResource;
break;
case GLDescriptorMapping::Tex2D:
prefix = "Tex2D";
dstLoc.category = DescriptorCategory::ReadOnlyResource;
break;
case GLDescriptorMapping::Tex3D:
prefix = "Tex3D";
dstLoc.category = DescriptorCategory::ReadOnlyResource;
break;
case GLDescriptorMapping::Tex1DArray:
prefix = "Tex1DArray";
dstLoc.category = DescriptorCategory::ReadOnlyResource;
break;
case GLDescriptorMapping::Tex2DArray:
prefix = "Tex2DArray";
dstLoc.category = DescriptorCategory::ReadOnlyResource;
break;
case GLDescriptorMapping::TexCubeArray:
prefix = "TexCubeArray";
dstLoc.category = DescriptorCategory::ReadOnlyResource;
break;
case GLDescriptorMapping::TexRect:
prefix = "TexRect";
dstLoc.category = DescriptorCategory::ReadOnlyResource;
break;
case GLDescriptorMapping::TexBuffer:
prefix = "TexBuffer";
dstLoc.category = DescriptorCategory::ReadOnlyResource;
break;
case GLDescriptorMapping::TexCube:
prefix = "TexCube";
dstLoc.category = DescriptorCategory::ReadOnlyResource;
break;
case GLDescriptorMapping::Tex2DMS:
prefix = "Tex2DMS";
dstLoc.category = DescriptorCategory::ReadOnlyResource;
break;
case GLDescriptorMapping::Tex2DMSArray:
prefix = "Tex2DMSArray";
dstLoc.category = DescriptorCategory::ReadOnlyResource;
break;
case GLDescriptorMapping::Images:
prefix = "Image";
dstLoc.category = DescriptorCategory::ReadWriteResource;
break;
case GLDescriptorMapping::AtomicCounter:
prefix = "Atomic";
dstLoc.category = DescriptorCategory::ReadWriteResource;
break;
case GLDescriptorMapping::ShaderStorage:
prefix = "SSBO";
dstLoc.category = DescriptorCategory::ReadWriteResource;
break;
case GLDescriptorMapping::Count:
case GLDescriptorMapping::Invalid: dstLoc.category = DescriptorCategory::Unknown; break;
}
dstLoc.fixedBindNumber = srcLoc.idx;
if(srcLoc.type == GLDescriptorMapping::BareUniforms)
dstLoc.logicalBindName =
StringFormat::Fmt("%s %s", prefix, ToStr(ShaderStage(srcLoc.idx)).c_str());
else
dstLoc.logicalBindName = StringFormat::Fmt("%s %u", prefix, srcLoc.idx);
}
}
return ret;
}
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 ShaderConstantType &desc = variables[i].type;
// 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.baseType;
var.flags = desc.flags;
const uint32_t matStride = desc.matrixByteStride;
if(!variables[i].type.members.empty())
{
if(desc.elements <= 1)
{
OpenGLFillCBufferVariables(shader, prog, bufferBacked, prefix + var.name.c_str() + ".",
variables[i].type.members, var.members, data);
var.type = VarType::Struct;
}
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 = VarType::Struct;
arrEl.flags = var.flags;
OpenGLFillCBufferVariables(shader, prog, bufferBacked, prefix + arrEl.name.c_str() + ".",
variables[i].type.members, arrEl.members, data);
}
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 this is an array of size 1 try looking for <array_variable_name>[0].<member_name>
if((idx == GL_INVALID_INDEX) && (desc.elements == 1))
{
rdcstr arrayZeroName = prefix;
if(!arrayZeroName.empty() && arrayZeroName.back() == '.')
arrayZeroName.pop_back();
arrayZeroName += "[0]." + var.name;
idx = GL.glGetProgramResourceIndex(prog, eGL_UNIFORM, arrayZeroName.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 > 1)
{
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);
elems.push_back(el);
}
var.members = elems;
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 <= 1)
{
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:
case VarType::Struct:
case VarType::Enum:
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:
case VarType::Struct:
case VarType::Enum:
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;
elems.push_back(el);
}
var.members = elems;
var.rows = var.columns = 0;
}
}
}
outvars.push_back(var);
}
}
void GLReplay::FillCBufferVariables(ResourceId pipeline, ResourceId shader, ShaderStage stage,
rdcstr entryPoint, uint32_t cbufSlot,
rdcarray<ShaderVariable> &outvars, const bytebuf &data)
{
WrappedOpenGL &drv = *m_pDriver;
MakeCurrentReplayContext(&m_ReplayCtx);
const WrappedOpenGL::ShaderData &shaderDetails = m_pDriver->GetShader(shader);
if((int32_t)cbufSlot >= shaderDetails.GetReflection()->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));
const WrappedOpenGL::PipelineData &pipeDetails = m_pDriver->GetPipeline(id);
size_t s = ShaderIdx(shaderDetails.type);
curProg = m_pDriver->GetResourceManager()->GetResource(pipeDetails.stagePrograms[s]).name;
}
}
const ConstantBlock &cblock = shaderDetails.GetReflection()->constantBlocks[cbufSlot];
if(shaderDetails.spirvWords.empty())
{
OpenGLFillCBufferVariables(shaderDetails.GetReflection()->resourceId, curProg,
cblock.bufferBacked ? true : false, "", cblock.variables, outvars,
data);
}
else
{
if(cblock.compileConstants)
{
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.GetReflection()->resourceId, shaderDetails.patchData,
cblock.variables, outvars, specconsts);
}
else if(!cblock.bufferBacked)
{
OpenGLFillCBufferVariables(shaderDetails.GetReflection()->resourceId, curProg, false, "",
cblock.variables, outvars, data);
}
else
{
StandardFillCBufferVariables(shaderDetails.GetReflection()->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);
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 || baseFormat == eGL_STENCIL_INDEX)
{
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);
if(baseFormat == eGL_STENCIL_INDEX)
drv.glColorMask(GL_TRUE, 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);
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);
}
// 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(ResourceId(),
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::SetCustomShaderIncludes(const rdcarray<rdcstr> &directories)
{
}
void GLReplay::BuildCustomShader(ShaderEncoding sourceEncoding, const bytebuf &source,
const rdcstr &entry, const ShaderCompileFlags &compileFlags,
ShaderStage type, ResourceId &id, rdcstr &errors)
{
if(sourceEncoding == ShaderEncoding::GLSL)
{
rdcstr sourceText = InsertSnippetAfterVersion(ShaderType::GLSL, (const char *)source.data(),
source.count(), GLSL_CUSTOM_PREFIX);
bytebuf patchedSource;
patchedSource.assign((byte *)sourceText.begin(), sourceText.size());
return BuildTargetShader(sourceEncoding, patchedSource, entry, compileFlags, type, id, errors);
}
BuildTargetShader(sourceEncoding, source, entry, compileFlags, type, id, errors);
}
ResourceId GLReplay::ApplyCustomShader(TextureDisplay &display)
{
if(display.customShaderId == ResourceId() || display.resourceId == ResourceId())
return ResourceId();
WrappedOpenGL::TextureData &texDetails = m_pDriver->m_Textures[display.resourceId];
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, display.subresource.mip);
m_pDriver->glViewport(0, 0, RDCMAX(1, texDetails.width >> display.subresource.mip),
RDCMAX(1, texDetails.height >> display.subresource.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 = display.customShaderId;
disp.resourceId = display.resourceId;
disp.typeCast = display.typeCast;
disp.hdrMultiplier = -1.0f;
disp.linearDisplayAsGamma = false;
disp.subresource = display.subresource;
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()->GetResource(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);
}
void GLReplay::ClearReplayCache()
{
ClearPostVSCache();
}
void GLReplay::ReloadShaderDebugInformation()
{
// Nop : GL does not have seperate debug information
}
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()->GetResource(texid).name;
WrappedOpenGL::TextureData &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, 1, &supported);
m_pDriver->glGetInternalformativ(target, fmt, eGL_FRAGMENT_TEXTURE, 1, &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, 1,
(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()->GetResource(bufid).name;
m_pDriver->glNamedBufferSubDataEXT(buf, 0, dataSize, data);
}
rdcarray<EventUsage> GLReplay::GetUsage(ResourceId id)
{
if((m_pDriver->m_Textures.find(id) == m_pDriver->m_Textures.end() ||
m_pDriver->m_Textures[id].resource.Namespace == eResUnknown) &&
(m_pDriver->m_Buffers.find(id) == m_pDriver->m_Buffers.end() ||
m_pDriver->m_Buffers[id].resource.Namespace == eResUnknown))
{
return {EventUsage(0, ResourceUsage::Unused)};
}
return m_pDriver->GetUsage(id);
}
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);
}
RDResult 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_ERROR_RESULT(ResultCode::FileCorrupted, "File does not contain captured API data");
ver = rdc->GetSectionProperties(sectionIdx).version;
if(!GLInitParams::IsSupportedVersion(ver))
{
RETURN_ERROR_RESULT(
ResultCode::APIIncompatibleVersion,
"Incompatible OpenGL serialise version %llu, newest version supported is %llu", ver,
GLInitParams::CurrentVersion);
}
StreamReader *reader = rdc->ReadSection(sectionIdx);
ReadSerialiser ser(reader, Ownership::Stream);
ser.SetVersion(ver);
SystemChunk chunk = ser.ReadChunk<SystemChunk>();
if(chunk != SystemChunk::DriverInit)
{
RETURN_ERROR_RESULT(ResultCode::FileCorrupted,
"Expected to get a DriverInit chunk, instead got %u", chunk);
}
SERIALISE_ELEMENT(initParams);
if(ser.IsErrored())
return ser.GetError();
if(!initParams.renderer.empty())
RDCLOG("Capture was created on %s / %s", initParams.renderer.c_str(),
initParams.version.c_str());
}
GLWindowingData data = {};
RDResult status = platform.InitialiseAPI(data, rdcdriver, opts.apiValidation);
// any errors will be already printed, just pass the error up
if(status != ResultCode::Succeeded)
return status;
bool current = platform.MakeContextCurrent(data);
if(!current)
{
platform.DeleteReplayContext(data);
RETURN_ERROR_RESULT(ResultCode::APIInitFailed, "Couldn't activate the created replay context");
}
// 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();
RDResult extensionsValidated = CheckReplayContext();
if(extensionsValidated != ResultCode::Succeeded)
{
platform.DeleteReplayContext(data);
return extensionsValidated;
}
RDResult functionsValidated = ValidateFunctionPointers();
if(functionsValidated != ResultCode::Succeeded)
{
platform.DeleteReplayContext(data);
return functionsValidated;
}
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_ERROR_RESULT(ResultCode::APIHardwareUnsupported, "Failed to create analysis context");
}
gldriver->Initialise(initParams, ver, opts);
*driver = (IReplayDriver *)replay;
return ResultCode::Succeeded;
}
RDResult GL_ProcessStructured(RDCFile *rdc, SDFile &output)
{
GLDummyPlatform dummy;
WrappedOpenGL device(dummy);
int sectionIdx = rdc->SectionIndex(SectionType::FrameCapture);
if(sectionIdx < 0)
RETURN_ERROR_RESULT(ResultCode::FileCorrupted, "File does not contain captured API data");
device.SetStructuredExport(rdc->GetSectionProperties(sectionIdx).version);
RDResult status = device.ReadLogInitialisation(rdc, true);
if(status == ResultCode::Succeeded)
device.GetStructuredFile()->Swap(output);
return status;
}
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)
RDResult 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)
{
RETURN_ERROR_RESULT(ResultCode::APIInitFailed,
"Couldn't find required EGL function addresses");
}
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)
{
RETURN_ERROR_RESULT(ResultCode::APIInitFailed,
"Couldn't find required GL function addresses");
}
return CreateReplayDevice(rdc ? rdc->GetDriver() : RDCDriver::OpenGLES, rdc, opts,
GetGLPlatform(), driver);
}
RETURN_ERROR_RESULT(ResultCode::APIInitFailed,
"libEGL not available, and GL cannot initialise or doesn't support "
"EXT_create_context_es2_profile");
#else
// no GL support, no fallback apart from EGL
RETURN_ERROR_RESULT(ResultCode::APIInitFailed, "libEGL is not available");
#endif
}
static DriverRegistration GLESDriverRegistration(RDCDriver::OpenGLES, &GLES_CreateReplayDevice);
#endif
#if defined(RENDERDOC_SUPPORT_GL)
RDResult 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
RETURN_ERROR_RESULT(ResultCode::InternalError,
"EGL support must be enabled at build time when using Wayland");
#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)
{
RETURN_ERROR_RESULT(ResultCode::APIInitFailed,
"Current platform doesn't support OpenGL contexts");
}
RDCDEBUG("Creating an OpenGL replay device");
bool load_ok = gl_platform->PopulateForReplay();
if(!load_ok)
{
RETURN_ERROR_RESULT(ResultCode::APIInitFailed,
"Couldn't find required platform %s function addresses",
gl_platform == &GetGLPlatform() ? "GL" : "EGL");
}
return CreateReplayDevice(rdc ? rdc->GetDriver() : RDCDriver::OpenGL, rdc, opts, *gl_platform,
driver);
}
static DriverRegistration GLDriverRegistration(RDCDriver::OpenGL, &GL_CreateReplayDevice);
#endif