void GLReplay::SavePipelineState()

in renderdoc/driver/gl/gl_replay.cpp [832:1977]


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->GetOriginalID(rm->GetResID(VertexArrayRes(ctx, vao)));

  GLuint ibuffer = 0;
  drv.glGetIntegerv(eGL_ELEMENT_ARRAY_BUFFER_BINDING, (GLint *)&ibuffer);
  pipe.vertexInput.indexBuffer = rm->GetOriginalID(rm->GetResID(BufferRes(ctx, ibuffer)));

  pipe.vertexInput.primitiveRestart = rs.Enabled[GLRenderState::eEnabled_PrimitiveRestart] ||
                                      rs.Enabled[GLRenderState::eEnabled_PrimitiveRestartFixedIndex];
  pipe.vertexInput.restartIndex = rs.Enabled[GLRenderState::eEnabled_PrimitiveRestartFixedIndex]
                                      ? ~0U
                                      : rs.PrimitiveRestartIndex;

  const GLDrawParams &drawParams = m_pDriver->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->GetOriginalID(rm->GetResID(BufferRes(ctx, buffer)));

    drv.glGetIntegeri_v(eGL_VERTEX_BINDING_STRIDE, i,
                        (GLint *)&pipe.vertexInput.vertexBuffers[i].byteStride);
    drv.glGetIntegeri_v(eGL_VERTEX_BINDING_OFFSET, i,
                        (GLint *)&pipe.vertexInput.vertexBuffers[i].byteOffset);
    drv.glGetIntegeri_v(eGL_VERTEX_BINDING_DIVISOR, i,
                        (GLint *)&pipe.vertexInput.vertexBuffers[i].instanceDivisor);
  }

  for(GLuint i = 0; i < (GLuint)numVAttribBindings; i++)
  {
    drv.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_ARRAY_ENABLED,
                            (GLint *)&pipe.vertexInput.attributes[i].enabled);
    drv.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_BINDING,
                            (GLint *)&pipe.vertexInput.attributes[i].vertexBufferSlot);
    drv.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_RELATIVE_OFFSET,
                            (GLint *)&pipe.vertexInput.attributes[i].byteOffset);

    GLenum type = eGL_FLOAT;
    GLint normalized = 0;

    drv.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_ARRAY_TYPE, (GLint *)&type);
    drv.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_ARRAY_NORMALIZED, &normalized);

    GLint integer = 0;
    drv.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_ARRAY_INTEGER, &integer);

    RDCEraseEl(pipe.vertexInput.attributes[i].genericValue);
    drv.glGetVertexAttribfv(i, eGL_CURRENT_VERTEX_ATTRIB,
                            (GLfloat *)pipe.vertexInput.attributes[i].genericValue.floatValue.data());

    ResourceFormat fmt;

    fmt.type = ResourceFormatType::Regular;
    GLint compCount;
    drv.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_ARRAY_SIZE, (GLint *)&compCount);

    fmt.compCount = (uint8_t)compCount;

    switch(type)
    {
      default:
      case eGL_BYTE:
        fmt.compByteWidth = 1;
        fmt.compType = CompType::SInt;
        break;
      case eGL_UNSIGNED_BYTE:
        fmt.compByteWidth = 1;
        fmt.compType = CompType::UInt;
        break;
      case eGL_SHORT:
        fmt.compByteWidth = 2;
        fmt.compType = CompType::SInt;
        break;
      case eGL_UNSIGNED_SHORT:
        fmt.compByteWidth = 2;
        fmt.compType = CompType::UInt;
        break;
      case eGL_INT:
        fmt.compByteWidth = 4;
        fmt.compType = CompType::SInt;
        break;
      case eGL_UNSIGNED_INT:
        fmt.compByteWidth = 4;
        fmt.compType = CompType::UInt;
        break;
      case eGL_FLOAT:
        fmt.compByteWidth = 4;
        fmt.compType = CompType::Float;
        break;
      case eGL_DOUBLE:
        fmt.compByteWidth = 8;
        fmt.compType = CompType::Float;
        break;
      case eGL_HALF_FLOAT:
        fmt.compByteWidth = 2;
        fmt.compType = CompType::Float;
        break;
      case eGL_INT_2_10_10_10_REV:
        fmt.type = ResourceFormatType::R10G10B10A2;
        fmt.compCount = 4;
        fmt.compType = CompType::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,
  };
  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->m_Pipelines[id];

        pipe.pipelineResourceId = rm->GetUnreplacedOriginalID(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->GetCurrentResource(pipeDetails.stagePrograms[i]).name;
          }
        }
      }
    }
    else
    {
      ResourceId id = rm->GetResID(ProgramRes(ctx, curProg));
      const WrappedOpenGL::ProgramData &progDetails = m_pDriver->m_Programs[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->GetCurrentResource(progIds[i]).name;
      stages[i]->programResourceId = rm->GetUnreplacedOriginalID(progIds[i]);
      stages[i]->shaderResourceId = rm->GetUnreplacedOriginalID(shadIds[i]);

      const WrappedOpenGL::ShaderData &shaderDetails = m_pDriver->m_Shaders[shadIds[i]];

      if(shaderDetails.reflection->resourceId == ResourceId())
        stages[i]->reflection = refls[i] = NULL;
      else
        stages[i]->reflection = refls[i] = shaderDetails.reflection;

      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++)
  {
    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;

      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->GetOriginalID(rm->GetResID(FeedbackRes(ctx, feedback)));
    else
      pipe.transformFeedback.feedbackResourceId = ResourceId();

    GLint maxCount = 0;
    drv.glGetIntegerv(eGL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS, &maxCount);

    for(int i = 0; i < (int)ARRAY_COUNT(pipe.transformFeedback.bufferResourceId) && i < maxCount; i++)
    {
      GLuint buffer = 0;
      drv.glGetIntegeri_v(eGL_TRANSFORM_FEEDBACK_BUFFER_BINDING, i, (GLint *)&buffer);
      pipe.transformFeedback.bufferResourceId[i] =
          rm->GetOriginalID(rm->GetResID(BufferRes(ctx, buffer)));
      drv.glGetInteger64i_v(eGL_TRANSFORM_FEEDBACK_BUFFER_START, i,
                            (GLint64 *)&pipe.transformFeedback.byteOffset[i]);
      drv.glGetInteger64i_v(eGL_TRANSFORM_FEEDBACK_BUFFER_SIZE, i,
                            (GLint64 *)&pipe.transformFeedback.byteSize[i]);
    }

    GLint p = 0;
    drv.glGetIntegerv(eGL_TRANSFORM_FEEDBACK_BUFFER_PAUSED, &p);
    pipe.transformFeedback.paused = (p != 0);

    drv.glGetIntegerv(eGL_TRANSFORM_FEEDBACK_BUFFER_ACTIVE, &p);
    pipe.transformFeedback.active = (p != 0) || m_pDriver->m_WasActiveFeedback;
  }

  for(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->GetOriginalID(rm->GetResID(FramebufferRes(ctx, curDrawFBO)));
    pipe.framebuffer.drawFBO.colorAttachments.resize(numCols);
    for(GLint i = 0; i < numCols; i++)
    {
      ResourceId id =
          rm->GetResID(rbCol[i] ? RenderbufferRes(ctx, curCol[i]) : TextureRes(ctx, curCol[i]));

      pipe.framebuffer.drawFBO.colorAttachments[i].resource = rm->GetOriginalID(id);

      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 = rm->GetOriginalID(id);
    pipe.framebuffer.drawFBO.stencilAttachment.resource = rm->GetOriginalID(
        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->GetOriginalID(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->GetOriginalID(
          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->GetOriginalID(
        rm->GetResID(rbDepth ? RenderbufferRes(ctx, curDepth) : TextureRes(ctx, curDepth)));
    pipe.framebuffer.readFBO.stencilAttachment.resource = rm->GetOriginalID(
        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];
}