blob: 02c276d9e264bc320d5a7fbb7d1229d7275a3af5 [file] [log] [blame]
//
// Copyright (c) 2014 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// RendererD3D.cpp: Implementation of the base D3D Renderer.
#include "libANGLE/renderer/d3d/RendererD3D.h"
#include "common/debug.h"
#include "common/MemoryBuffer.h"
#include "common/utilities.h"
#include "libANGLE/Display.h"
#include "libANGLE/formatutils.h"
#include "libANGLE/Framebuffer.h"
#include "libANGLE/FramebufferAttachment.h"
#include "libANGLE/renderer/d3d/BufferD3D.h"
#include "libANGLE/renderer/d3d/CompilerD3D.h"
#include "libANGLE/renderer/d3d/DeviceD3D.h"
#include "libANGLE/renderer/d3d/DisplayD3D.h"
#include "libANGLE/renderer/d3d/IndexDataManager.h"
#include "libANGLE/renderer/d3d/ProgramD3D.h"
#include "libANGLE/renderer/d3d/SamplerD3D.h"
#include "libANGLE/ResourceManager.h"
#include "libANGLE/State.h"
#include "libANGLE/VertexArray.h"
namespace rx
{
namespace
{
// If we request a scratch buffer requesting a smaller size this many times,
// release and recreate the scratch buffer. This ensures we don't have a
// degenerate case where we are stuck hogging memory.
const int ScratchMemoryBufferLifetime = 1000;
} // anonymous namespace
const uintptr_t RendererD3D::DirtyPointer = std::numeric_limits<uintptr_t>::max();
RendererD3D::RendererD3D(egl::Display *display)
: mDisplay(display),
mDeviceLost(false),
mAnnotator(nullptr),
mScratchMemoryBufferResetCounter(0),
mWorkaroundsInitialized(false),
mEGLDevice(nullptr)
{
}
RendererD3D::~RendererD3D()
{
cleanup();
}
void RendererD3D::cleanup()
{
SafeDelete(mEGLDevice);
mScratchMemoryBuffer.resize(0);
for (auto &incompleteTexture : mIncompleteTextures)
{
incompleteTexture.second.set(NULL);
}
mIncompleteTextures.clear();
if (mAnnotator != nullptr)
{
gl::UninitializeDebugAnnotations();
SafeDelete(mAnnotator);
}
}
CompilerImpl *RendererD3D::createCompiler()
{
return new CompilerD3D(getRendererClass());
}
SamplerImpl *RendererD3D::createSampler()
{
return new SamplerD3D();
}
gl::Error RendererD3D::drawArrays(const gl::Data &data, GLenum mode, GLint first, GLsizei count)
{
return genericDrawArrays(data, mode, first, count, 0);
}
gl::Error RendererD3D::drawArraysInstanced(const gl::Data &data,
GLenum mode,
GLint first,
GLsizei count,
GLsizei instanceCount)
{
return genericDrawArrays(data, mode, first, count, instanceCount);
}
gl::Error RendererD3D::drawElements(const gl::Data &data,
GLenum mode,
GLsizei count,
GLenum type,
const GLvoid *indices,
const gl::IndexRange &indexRange)
{
return genericDrawElements(data, mode, count, type, indices, 0, indexRange);
}
gl::Error RendererD3D::drawElementsInstanced(const gl::Data &data,
GLenum mode,
GLsizei count,
GLenum type,
const GLvoid *indices,
GLsizei instances,
const gl::IndexRange &indexRange)
{
return genericDrawElements(data, mode, count, type, indices, instances, indexRange);
}
gl::Error RendererD3D::drawRangeElements(const gl::Data &data,
GLenum mode,
GLuint start,
GLuint end,
GLsizei count,
GLenum type,
const GLvoid *indices,
const gl::IndexRange &indexRange)
{
return genericDrawElements(data, mode, count, type, indices, 0, indexRange);
}
gl::Error RendererD3D::genericDrawElements(const gl::Data &data,
GLenum mode,
GLsizei count,
GLenum type,
const GLvoid *indices,
GLsizei instances,
const gl::IndexRange &indexRange)
{
gl::Program *program = data.state->getProgram();
ASSERT(program != nullptr);
ProgramD3D *programD3D = GetImplAs<ProgramD3D>(program);
bool usesPointSize = programD3D->usesPointSize();
programD3D->updateSamplerMapping();
gl::Error error = generateSwizzles(data);
if (error.isError())
{
return error;
}
if (!applyPrimitiveType(mode, count, usesPointSize))
{
return gl::Error(GL_NO_ERROR);
}
error = applyRenderTarget(data, mode, false);
if (error.isError())
{
return error;
}
error = applyState(data, mode);
if (error.isError())
{
return error;
}
TranslatedIndexData indexInfo;
indexInfo.indexRange = indexRange;
SourceIndexData sourceIndexInfo;
error = applyIndexBuffer(data, indices, count, mode, type, &indexInfo, &sourceIndexInfo);
if (error.isError())
{
return error;
}
applyTransformFeedbackBuffers(*data.state);
// Transform feedback is not allowed for DrawElements, this error should have been caught at the API validation
// layer.
ASSERT(!data.state->isTransformFeedbackActiveUnpaused());
size_t vertexCount = indexInfo.indexRange.vertexCount();
error = applyVertexBuffer(*data.state, mode, static_cast<GLsizei>(indexInfo.indexRange.start),
static_cast<GLsizei>(vertexCount), instances, &sourceIndexInfo);
if (error.isError())
{
return error;
}
error = applyShaders(data, mode);
if (error.isError())
{
return error;
}
error = applyTextures(data);
if (error.isError())
{
return error;
}
error = programD3D->applyUniformBuffers(data);
if (error.isError())
{
return error;
}
if (!skipDraw(data, mode))
{
error = drawElementsImpl(data, indexInfo, mode, count, type, indices, instances);
if (error.isError())
{
return error;
}
}
return gl::Error(GL_NO_ERROR);
}
gl::Error RendererD3D::genericDrawArrays(const gl::Data &data,
GLenum mode,
GLint first,
GLsizei count,
GLsizei instances)
{
gl::Program *program = data.state->getProgram();
ASSERT(program != nullptr);
ProgramD3D *programD3D = GetImplAs<ProgramD3D>(program);
bool usesPointSize = programD3D->usesPointSize();
programD3D->updateSamplerMapping();
gl::Error error = generateSwizzles(data);
if (error.isError())
{
return error;
}
if (!applyPrimitiveType(mode, count, usesPointSize))
{
return gl::Error(GL_NO_ERROR);
}
error = applyRenderTarget(data, mode, false);
if (error.isError())
{
return error;
}
error = applyState(data, mode);
if (error.isError())
{
return error;
}
applyTransformFeedbackBuffers(*data.state);
error = applyVertexBuffer(*data.state, mode, first, count, instances, nullptr);
if (error.isError())
{
return error;
}
error = applyShaders(data, mode);
if (error.isError())
{
return error;
}
error = applyTextures(data);
if (error.isError())
{
return error;
}
error = programD3D->applyUniformBuffers(data);
if (error.isError())
{
return error;
}
if (!skipDraw(data, mode))
{
error = drawArraysImpl(data, mode, count, instances);
if (error.isError())
{
return error;
}
if (data.state->isTransformFeedbackActiveUnpaused())
{
markTransformFeedbackUsage(data);
}
}
return gl::Error(GL_NO_ERROR);
}
gl::Error RendererD3D::generateSwizzles(const gl::Data &data, gl::SamplerType type)
{
ProgramD3D *programD3D = GetImplAs<ProgramD3D>(data.state->getProgram());
unsigned int samplerRange = static_cast<unsigned int>(programD3D->getUsedSamplerRange(type));
for (unsigned int i = 0; i < samplerRange; i++)
{
GLenum textureType = programD3D->getSamplerTextureType(type, i);
GLint textureUnit = programD3D->getSamplerMapping(type, i, *data.caps);
if (textureUnit != -1)
{
gl::Texture *texture = data.state->getSamplerTexture(textureUnit, textureType);
ASSERT(texture);
if (texture->getTextureState().swizzleRequired())
{
gl::Error error = generateSwizzle(texture);
if (error.isError())
{
return error;
}
}
}
}
return gl::Error(GL_NO_ERROR);
}
gl::Error RendererD3D::generateSwizzles(const gl::Data &data)
{
gl::Error error = generateSwizzles(data, gl::SAMPLER_VERTEX);
if (error.isError())
{
return error;
}
error = generateSwizzles(data, gl::SAMPLER_PIXEL);
if (error.isError())
{
return error;
}
return gl::Error(GL_NO_ERROR);
}
// Applies the render target surface, depth stencil surface, viewport rectangle and
// scissor rectangle to the renderer
gl::Error RendererD3D::applyRenderTarget(const gl::Data &data, GLenum drawMode, bool ignoreViewport)
{
const gl::Framebuffer *framebufferObject = data.state->getDrawFramebuffer();
ASSERT(framebufferObject && framebufferObject->checkStatus(data) == GL_FRAMEBUFFER_COMPLETE);
gl::Error error = applyRenderTarget(framebufferObject);
if (error.isError())
{
return error;
}
float nearZ = data.state->getNearPlane();
float farZ = data.state->getFarPlane();
setViewport(data.state->getViewport(), nearZ, farZ, drawMode,
data.state->getRasterizerState().frontFace, ignoreViewport);
setScissorRectangle(data.state->getScissor(), data.state->isScissorTestEnabled());
return gl::Error(GL_NO_ERROR);
}
// Applies the fixed-function state (culling, depth test, alpha blending, stenciling, etc) to the Direct3D device
gl::Error RendererD3D::applyState(const gl::Data &data, GLenum drawMode)
{
const gl::Framebuffer *framebufferObject = data.state->getDrawFramebuffer();
int samples = framebufferObject->getSamples(data);
gl::RasterizerState rasterizer = data.state->getRasterizerState();
rasterizer.pointDrawMode = (drawMode == GL_POINTS);
rasterizer.multiSample = (samples != 0);
gl::Error error = setRasterizerState(rasterizer);
if (error.isError())
{
return error;
}
unsigned int mask = 0;
if (data.state->isSampleCoverageEnabled())
{
GLclampf coverageValue = data.state->getSampleCoverageValue();
if (coverageValue != 0)
{
float threshold = 0.5f;
for (int i = 0; i < samples; ++i)
{
mask <<= 1;
if ((i + 1) * coverageValue >= threshold)
{
threshold += 1.0f;
mask |= 1;
}
}
}
bool coverageInvert = data.state->getSampleCoverageInvert();
if (coverageInvert)
{
mask = ~mask;
}
}
else
{
mask = 0xFFFFFFFF;
}
error = setBlendState(framebufferObject, data.state->getBlendState(),
data.state->getBlendColor(), mask);
if (error.isError())
{
return error;
}
error = setDepthStencilState(*data.state);
if (error.isError())
{
return error;
}
return gl::Error(GL_NO_ERROR);
}
// Applies the shaders and shader constants to the Direct3D device
gl::Error RendererD3D::applyShaders(const gl::Data &data, GLenum drawMode)
{
gl::Program *program = data.state->getProgram();
ProgramD3D *programD3D = GetImplAs<ProgramD3D>(program);
programD3D->updateCachedInputLayout(*data.state);
gl::Error error = applyShadersImpl(data, drawMode);
if (error.isError())
{
return error;
}
return programD3D->applyUniforms(drawMode);
}
// For each Direct3D sampler of either the pixel or vertex stage,
// looks up the corresponding OpenGL texture image unit and texture type,
// and sets the texture and its addressing/filtering state (or NULL when inactive).
gl::Error RendererD3D::applyTextures(const gl::Data &data, gl::SamplerType shaderType,
const FramebufferTextureArray &framebufferTextures, size_t framebufferTextureCount)
{
ProgramD3D *programD3D = GetImplAs<ProgramD3D>(data.state->getProgram());
unsigned int samplerRange = programD3D->getUsedSamplerRange(shaderType);
for (unsigned int samplerIndex = 0; samplerIndex < samplerRange; samplerIndex++)
{
GLenum textureType = programD3D->getSamplerTextureType(shaderType, samplerIndex);
GLint textureUnit = programD3D->getSamplerMapping(shaderType, samplerIndex, *data.caps);
if (textureUnit != -1)
{
gl::Texture *texture = data.state->getSamplerTexture(textureUnit, textureType);
ASSERT(texture);
gl::Sampler *samplerObject = data.state->getSampler(textureUnit);
const gl::SamplerState &samplerState =
samplerObject ? samplerObject->getSamplerState() : texture->getSamplerState();
// TODO: std::binary_search may become unavailable using older versions of GCC
if (texture->isSamplerComplete(samplerState, data) &&
!std::binary_search(framebufferTextures.begin(),
framebufferTextures.begin() + framebufferTextureCount, texture))
{
gl::Error error = setSamplerState(shaderType, samplerIndex, texture, samplerState);
if (error.isError())
{
return error;
}
error = setTexture(shaderType, samplerIndex, texture);
if (error.isError())
{
return error;
}
}
else
{
// Texture is not sampler complete or it is in use by the framebuffer. Bind the incomplete texture.
gl::Texture *incompleteTexture = getIncompleteTexture(textureType);
gl::Error error = setTexture(shaderType, samplerIndex, incompleteTexture);
if (error.isError())
{
return error;
}
}
}
else
{
// No texture bound to this slot even though it is used by the shader, bind a NULL texture
gl::Error error = setTexture(shaderType, samplerIndex, NULL);
if (error.isError())
{
return error;
}
}
}
// Set all the remaining textures to NULL
size_t samplerCount = (shaderType == gl::SAMPLER_PIXEL) ? data.caps->maxTextureImageUnits
: data.caps->maxVertexTextureImageUnits;
clearTextures(shaderType, samplerRange, samplerCount);
return gl::Error(GL_NO_ERROR);
}
gl::Error RendererD3D::applyTextures(const gl::Data &data)
{
FramebufferTextureArray framebufferTextures;
size_t framebufferSerialCount = getBoundFramebufferTextures(data, &framebufferTextures);
gl::Error error = applyTextures(data, gl::SAMPLER_VERTEX, framebufferTextures, framebufferSerialCount);
if (error.isError())
{
return error;
}
error = applyTextures(data, gl::SAMPLER_PIXEL, framebufferTextures, framebufferSerialCount);
if (error.isError())
{
return error;
}
return gl::Error(GL_NO_ERROR);
}
bool RendererD3D::skipDraw(const gl::Data &data, GLenum drawMode)
{
const gl::State &state = *data.state;
if (drawMode == GL_POINTS)
{
bool usesPointSize = GetImplAs<ProgramD3D>(state.getProgram())->usesPointSize();
// ProgramBinary assumes non-point rendering if gl_PointSize isn't written,
// which affects varying interpolation. Since the value of gl_PointSize is
// undefined when not written, just skip drawing to avoid unexpected results.
if (!usesPointSize && !state.isTransformFeedbackActiveUnpaused())
{
// This is stictly speaking not an error, but developers should be
// notified of risking undefined behavior.
ERR("Point rendering without writing to gl_PointSize.");
return true;
}
}
else if (gl::IsTriangleMode(drawMode))
{
if (state.getRasterizerState().cullFace &&
state.getRasterizerState().cullMode == GL_FRONT_AND_BACK)
{
return true;
}
}
return false;
}
void RendererD3D::markTransformFeedbackUsage(const gl::Data &data)
{
const gl::TransformFeedback *transformFeedback = data.state->getCurrentTransformFeedback();
for (size_t i = 0; i < transformFeedback->getIndexedBufferCount(); i++)
{
const OffsetBindingPointer<gl::Buffer> &binding = transformFeedback->getIndexedBuffer(i);
if (binding.get() != nullptr)
{
BufferD3D *bufferD3D = GetImplAs<BufferD3D>(binding.get());
bufferD3D->markTransformFeedbackUsage();
}
}
}
size_t RendererD3D::getBoundFramebufferTextures(const gl::Data &data, FramebufferTextureArray *outTextureArray)
{
size_t textureCount = 0;
const gl::Framebuffer *drawFramebuffer = data.state->getDrawFramebuffer();
for (size_t i = 0; i < drawFramebuffer->getNumColorBuffers(); i++)
{
const gl::FramebufferAttachment *attachment = drawFramebuffer->getColorbuffer(i);
if (attachment && attachment->type() == GL_TEXTURE)
{
(*outTextureArray)[textureCount++] = attachment->getTexture();
}
}
const gl::FramebufferAttachment *depthStencilAttachment = drawFramebuffer->getDepthOrStencilbuffer();
if (depthStencilAttachment && depthStencilAttachment->type() == GL_TEXTURE)
{
(*outTextureArray)[textureCount++] = depthStencilAttachment->getTexture();
}
std::sort(outTextureArray->begin(), outTextureArray->begin() + textureCount);
return textureCount;
}
gl::Texture *RendererD3D::getIncompleteTexture(GLenum type)
{
if (mIncompleteTextures.find(type) == mIncompleteTextures.end())
{
const GLubyte color[] = { 0, 0, 0, 255 };
const gl::Extents colorSize(1, 1, 1);
const gl::PixelUnpackState unpack(1, 0);
const gl::Box area(0, 0, 0, 1, 1, 1);
// Skip the API layer to avoid needing to pass the Context and mess with dirty bits.
gl::Texture *t =
new gl::Texture(createTexture(type), std::numeric_limits<GLuint>::max(), type);
t->setStorage(type, 1, GL_RGBA8, colorSize);
if (type == GL_TEXTURE_CUBE_MAP)
{
for (GLenum face = GL_TEXTURE_CUBE_MAP_POSITIVE_X; face <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z; face++)
{
t->getImplementation()->setSubImage(face, 0, area, GL_RGBA8, GL_UNSIGNED_BYTE,
unpack, color);
}
}
else
{
t->getImplementation()->setSubImage(type, 0, area, GL_RGBA8, GL_UNSIGNED_BYTE, unpack,
color);
}
mIncompleteTextures[type].set(t);
}
return mIncompleteTextures[type].get();
}
bool RendererD3D::isDeviceLost() const
{
return mDeviceLost;
}
void RendererD3D::notifyDeviceLost()
{
mDeviceLost = true;
mDisplay->notifyDeviceLost();
}
std::string RendererD3D::getVendorString() const
{
LUID adapterLuid = { 0 };
if (getLUID(&adapterLuid))
{
char adapterLuidString[64];
sprintf_s(adapterLuidString, sizeof(adapterLuidString), "(adapter LUID: %08x%08x)", adapterLuid.HighPart, adapterLuid.LowPart);
return std::string(adapterLuidString);
}
return std::string("");
}
gl::Error RendererD3D::getScratchMemoryBuffer(size_t requestedSize, MemoryBuffer **bufferOut)
{
if (mScratchMemoryBuffer.size() == requestedSize)
{
mScratchMemoryBufferResetCounter = ScratchMemoryBufferLifetime;
*bufferOut = &mScratchMemoryBuffer;
return gl::Error(GL_NO_ERROR);
}
if (mScratchMemoryBuffer.size() > requestedSize)
{
mScratchMemoryBufferResetCounter--;
}
if (mScratchMemoryBufferResetCounter <= 0 || mScratchMemoryBuffer.size() < requestedSize)
{
mScratchMemoryBuffer.resize(0);
if (!mScratchMemoryBuffer.resize(requestedSize))
{
return gl::Error(GL_OUT_OF_MEMORY, "Failed to allocate internal buffer.");
}
mScratchMemoryBufferResetCounter = ScratchMemoryBufferLifetime;
}
ASSERT(mScratchMemoryBuffer.size() >= requestedSize);
*bufferOut = &mScratchMemoryBuffer;
return gl::Error(GL_NO_ERROR);
}
void RendererD3D::insertEventMarker(GLsizei length, const char *marker)
{
std::vector<wchar_t> wcstring (length + 1);
size_t convertedChars = 0;
errno_t err = mbstowcs_s(&convertedChars, wcstring.data(), length + 1, marker, _TRUNCATE);
if (err == 0)
{
getAnnotator()->setMarker(wcstring.data());
}
}
void RendererD3D::pushGroupMarker(GLsizei length, const char *marker)
{
std::vector<wchar_t> wcstring(length + 1);
size_t convertedChars = 0;
errno_t err = mbstowcs_s(&convertedChars, wcstring.data(), length + 1, marker, _TRUNCATE);
if (err == 0)
{
getAnnotator()->beginEvent(wcstring.data());
}
}
void RendererD3D::popGroupMarker()
{
getAnnotator()->endEvent();
}
void RendererD3D::initializeDebugAnnotator()
{
createAnnotator();
ASSERT(mAnnotator);
gl::InitializeDebugAnnotations(mAnnotator);
}
gl::DebugAnnotator *RendererD3D::getAnnotator()
{
ASSERT(mAnnotator);
return mAnnotator;
}
egl::Error RendererD3D::getEGLDevice(DeviceImpl **device)
{
if (mEGLDevice == nullptr)
{
egl::Error error = createEGLDevice(&mEGLDevice);
if (error.isError())
{
return error;
}
}
*device = static_cast<DeviceImpl *>(mEGLDevice);
return egl::Error(EGL_SUCCESS);
}
}