This change also fixes an issue with RGBA16F bitmaps when modulated with a color (for instance by setting an alpha on the Paint object). The color space conversion is currently done entirely in the shader, by doing these operations in order: 1. Sample the texture 2. Un-premultiply alpha 3. Apply the EOTF 4. Multiply by the 3x3 color space matrix 5. Apply the OETF 6. Premultiply alpha Optimizations: - Steps 2 & 6 are skipped for opaque (common) bitmaps - Step 3 is skipped when the color space's EOTF is close to sRGB (Display P3 for instance). Instead, we use a hardware sRGB fetch (when the GPU supports it) - When step 3 is necessary, we use one of four standard EOTF implementations, to save cycles when possible: + Linear (doesn't do anything) + Full parametric (ICC parametric curve type 4 as defined in ICC.1:2004-10, section 10.15) + Limited parametric (ICC parametric curve type 3) + Gamma (ICC parametric curve type 0) Color space conversion could be done using texture samplers instead, for instance 3D LUTs, with or without transfer functions baked in, or 1D LUTs for transfer functions. This would result in dependent texture fetches which may or may not be an advantage over an ALU based implementation. The current solution favor the use of ALUs to save precious bandwidth. Test: CtsUiRenderingTests, CtsGraphicsTests Bug: 32984164 Change-Id: I10bc3db515e13973b45220f129c66b23f0f7f8fe
456 lines
16 KiB
C++
456 lines
16 KiB
C++
/*
|
|
* Copyright (C) 2014 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
#include "DeferredLayerUpdater.h"
|
|
#include "GlLayer.h"
|
|
#include "VkLayer.h"
|
|
#include <GpuMemoryTracker.h>
|
|
#include "renderstate/RenderState.h"
|
|
|
|
#include "renderthread/CanvasContext.h"
|
|
#include "renderthread/EglManager.h"
|
|
#include "utils/GLUtils.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#include <ui/ColorSpace.h>
|
|
|
|
namespace android {
|
|
namespace uirenderer {
|
|
|
|
RenderState::RenderState(renderthread::RenderThread& thread)
|
|
: mRenderThread(thread)
|
|
, mViewportWidth(0)
|
|
, mViewportHeight(0)
|
|
, mFramebuffer(0) {
|
|
mThreadId = pthread_self();
|
|
}
|
|
|
|
RenderState::~RenderState() {
|
|
LOG_ALWAYS_FATAL_IF(mBlend || mMeshState || mScissor || mStencil,
|
|
"State object lifecycle not managed correctly");
|
|
}
|
|
|
|
void RenderState::onGLContextCreated() {
|
|
LOG_ALWAYS_FATAL_IF(mBlend || mMeshState || mScissor || mStencil,
|
|
"State object lifecycle not managed correctly");
|
|
GpuMemoryTracker::onGpuContextCreated();
|
|
|
|
mBlend = new Blend();
|
|
mMeshState = new MeshState();
|
|
mScissor = new Scissor();
|
|
mStencil = new Stencil();
|
|
|
|
// This is delayed because the first access of Caches makes GL calls
|
|
if (!mCaches) {
|
|
mCaches = &Caches::createInstance(*this);
|
|
}
|
|
mCaches->init();
|
|
}
|
|
|
|
static void layerLostGlContext(Layer* layer) {
|
|
LOG_ALWAYS_FATAL_IF(layer->getApi() != Layer::Api::OpenGL,
|
|
"layerLostGlContext on non GL layer");
|
|
static_cast<GlLayer*>(layer)->onGlContextLost();
|
|
}
|
|
|
|
void RenderState::onGLContextDestroyed() {
|
|
mLayerPool.clear();
|
|
|
|
// TODO: reset all cached state in state objects
|
|
std::for_each(mActiveLayers.begin(), mActiveLayers.end(), layerLostGlContext);
|
|
|
|
mCaches->terminate();
|
|
|
|
delete mBlend;
|
|
mBlend = nullptr;
|
|
delete mMeshState;
|
|
mMeshState = nullptr;
|
|
delete mScissor;
|
|
mScissor = nullptr;
|
|
delete mStencil;
|
|
mStencil = nullptr;
|
|
|
|
destroyLayersInUpdater();
|
|
GpuMemoryTracker::onGpuContextDestroyed();
|
|
}
|
|
|
|
void RenderState::onVkContextCreated() {
|
|
LOG_ALWAYS_FATAL_IF(mBlend || mMeshState || mScissor || mStencil,
|
|
"State object lifecycle not managed correctly");
|
|
GpuMemoryTracker::onGpuContextCreated();
|
|
}
|
|
|
|
static void layerDestroyedVkContext(Layer* layer) {
|
|
LOG_ALWAYS_FATAL_IF(layer->getApi() != Layer::Api::Vulkan,
|
|
"layerLostVkContext on non Vulkan layer");
|
|
static_cast<VkLayer*>(layer)->onVkContextDestroyed();
|
|
}
|
|
|
|
void RenderState::onVkContextDestroyed() {
|
|
mLayerPool.clear();
|
|
std::for_each(mActiveLayers.begin(), mActiveLayers.end(), layerDestroyedVkContext);
|
|
GpuMemoryTracker::onGpuContextDestroyed();
|
|
}
|
|
|
|
GrContext* RenderState::getGrContext() const {
|
|
return mRenderThread.getGrContext();
|
|
}
|
|
|
|
void RenderState::flush(Caches::FlushMode mode) {
|
|
switch (mode) {
|
|
case Caches::FlushMode::Full:
|
|
// fall through
|
|
case Caches::FlushMode::Moderate:
|
|
// fall through
|
|
case Caches::FlushMode::Layers:
|
|
mLayerPool.clear();
|
|
break;
|
|
}
|
|
mCaches->flush(mode);
|
|
}
|
|
|
|
void RenderState::setViewport(GLsizei width, GLsizei height) {
|
|
mViewportWidth = width;
|
|
mViewportHeight = height;
|
|
glViewport(0, 0, mViewportWidth, mViewportHeight);
|
|
}
|
|
|
|
|
|
void RenderState::getViewport(GLsizei* outWidth, GLsizei* outHeight) {
|
|
*outWidth = mViewportWidth;
|
|
*outHeight = mViewportHeight;
|
|
}
|
|
|
|
void RenderState::bindFramebuffer(GLuint fbo) {
|
|
if (mFramebuffer != fbo) {
|
|
mFramebuffer = fbo;
|
|
glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
|
|
}
|
|
}
|
|
|
|
GLuint RenderState::createFramebuffer() {
|
|
GLuint ret;
|
|
glGenFramebuffers(1, &ret);
|
|
return ret;
|
|
}
|
|
|
|
void RenderState::deleteFramebuffer(GLuint fbo) {
|
|
if (mFramebuffer == fbo) {
|
|
// GL defines that deleting the currently bound FBO rebinds FBO 0.
|
|
// Reflect this in our cached value.
|
|
mFramebuffer = 0;
|
|
}
|
|
glDeleteFramebuffers(1, &fbo);
|
|
}
|
|
|
|
void RenderState::invokeFunctor(Functor* functor, DrawGlInfo::Mode mode, DrawGlInfo* info) {
|
|
if (mode == DrawGlInfo::kModeProcessNoContext) {
|
|
// If there's no context we don't need to interrupt as there's
|
|
// no gl state to save/restore
|
|
(*functor)(mode, info);
|
|
} else {
|
|
interruptForFunctorInvoke();
|
|
(*functor)(mode, info);
|
|
resumeFromFunctorInvoke();
|
|
}
|
|
}
|
|
|
|
void RenderState::interruptForFunctorInvoke() {
|
|
mCaches->setProgram(nullptr);
|
|
mCaches->textureState().resetActiveTexture();
|
|
meshState().unbindMeshBuffer();
|
|
meshState().unbindIndicesBuffer();
|
|
meshState().resetVertexPointers();
|
|
meshState().disableTexCoordsVertexArray();
|
|
debugOverdraw(false, false);
|
|
// TODO: We need a way to know whether the functor is sRGB aware (b/32072673)
|
|
if (mCaches->extensions().hasLinearBlending() &&
|
|
mCaches->extensions().hasSRGBWriteControl()) {
|
|
glDisable(GL_FRAMEBUFFER_SRGB_EXT);
|
|
}
|
|
}
|
|
|
|
void RenderState::resumeFromFunctorInvoke() {
|
|
if (mCaches->extensions().hasLinearBlending() &&
|
|
mCaches->extensions().hasSRGBWriteControl()) {
|
|
glEnable(GL_FRAMEBUFFER_SRGB_EXT);
|
|
}
|
|
|
|
glViewport(0, 0, mViewportWidth, mViewportHeight);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
|
|
debugOverdraw(false, false);
|
|
|
|
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
|
|
|
scissor().invalidate();
|
|
blend().invalidate();
|
|
|
|
mCaches->textureState().activateTexture(0);
|
|
mCaches->textureState().resetBoundTextures();
|
|
}
|
|
|
|
void RenderState::debugOverdraw(bool enable, bool clear) {
|
|
if (Properties::debugOverdraw && mFramebuffer == 0) {
|
|
if (clear) {
|
|
scissor().setEnabled(false);
|
|
stencil().clear();
|
|
}
|
|
if (enable) {
|
|
stencil().enableDebugWrite();
|
|
} else {
|
|
stencil().disable();
|
|
}
|
|
}
|
|
}
|
|
|
|
static void destroyLayerInUpdater(DeferredLayerUpdater* layerUpdater) {
|
|
layerUpdater->destroyLayer();
|
|
}
|
|
|
|
void RenderState::destroyLayersInUpdater() {
|
|
std::for_each(mActiveLayerUpdaters.begin(), mActiveLayerUpdaters.end(), destroyLayerInUpdater);
|
|
}
|
|
|
|
class DecStrongTask : public renderthread::RenderTask {
|
|
public:
|
|
explicit DecStrongTask(VirtualLightRefBase* object) : mObject(object) {}
|
|
|
|
virtual void run() override {
|
|
mObject->decStrong(nullptr);
|
|
mObject = nullptr;
|
|
delete this;
|
|
}
|
|
|
|
private:
|
|
VirtualLightRefBase* mObject;
|
|
};
|
|
|
|
void RenderState::postDecStrong(VirtualLightRefBase* object) {
|
|
if (pthread_equal(mThreadId, pthread_self())) {
|
|
object->decStrong(nullptr);
|
|
} else {
|
|
mRenderThread.queue(new DecStrongTask(object));
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Render
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
void RenderState::render(const Glop& glop, const Matrix4& orthoMatrix) {
|
|
const Glop::Mesh& mesh = glop.mesh;
|
|
const Glop::Mesh::Vertices& vertices = mesh.vertices;
|
|
const Glop::Mesh::Indices& indices = mesh.indices;
|
|
const Glop::Fill& fill = glop.fill;
|
|
|
|
GL_CHECKPOINT(MODERATE);
|
|
|
|
// ---------------------------------------------
|
|
// ---------- Program + uniform setup ----------
|
|
// ---------------------------------------------
|
|
mCaches->setProgram(fill.program);
|
|
|
|
if (fill.colorEnabled) {
|
|
fill.program->setColor(fill.color);
|
|
}
|
|
|
|
fill.program->set(orthoMatrix,
|
|
glop.transform.modelView,
|
|
glop.transform.meshTransform(),
|
|
glop.transform.transformFlags & TransformFlags::OffsetByFudgeFactor);
|
|
|
|
// Color filter uniforms
|
|
if (fill.filterMode == ProgramDescription::ColorFilterMode::Blend) {
|
|
const FloatColor& color = fill.filter.color;
|
|
glUniform4f(mCaches->program().getUniform("colorBlend"),
|
|
color.r, color.g, color.b, color.a);
|
|
} else if (fill.filterMode == ProgramDescription::ColorFilterMode::Matrix) {
|
|
glUniformMatrix4fv(mCaches->program().getUniform("colorMatrix"), 1, GL_FALSE,
|
|
fill.filter.matrix.matrix);
|
|
glUniform4fv(mCaches->program().getUniform("colorMatrixVector"), 1,
|
|
fill.filter.matrix.vector);
|
|
}
|
|
|
|
// Round rect clipping uniforms
|
|
if (glop.roundRectClipState) {
|
|
// TODO: avoid query, and cache values (or RRCS ptr) in program
|
|
const RoundRectClipState* state = glop.roundRectClipState;
|
|
const Rect& innerRect = state->innerRect;
|
|
glUniform4f(fill.program->getUniform("roundRectInnerRectLTRB"),
|
|
innerRect.left, innerRect.top,
|
|
innerRect.right, innerRect.bottom);
|
|
glUniformMatrix4fv(fill.program->getUniform("roundRectInvTransform"),
|
|
1, GL_FALSE, &state->matrix.data[0]);
|
|
|
|
// add half pixel to round out integer rect space to cover pixel centers
|
|
float roundedOutRadius = state->radius + 0.5f;
|
|
glUniform1f(fill.program->getUniform("roundRectRadius"),
|
|
roundedOutRadius);
|
|
}
|
|
|
|
GL_CHECKPOINT(MODERATE);
|
|
|
|
// --------------------------------
|
|
// ---------- Mesh setup ----------
|
|
// --------------------------------
|
|
// vertices
|
|
meshState().bindMeshBuffer(vertices.bufferObject);
|
|
meshState().bindPositionVertexPointer(vertices.position, vertices.stride);
|
|
|
|
// indices
|
|
meshState().bindIndicesBuffer(indices.bufferObject);
|
|
|
|
// texture
|
|
if (fill.texture.texture != nullptr) {
|
|
const Glop::Fill::TextureData& texture = fill.texture;
|
|
// texture always takes slot 0, shader samplers increment from there
|
|
mCaches->textureState().activateTexture(0);
|
|
|
|
mCaches->textureState().bindTexture(texture.texture->target(), texture.texture->id());
|
|
if (texture.clamp != GL_INVALID_ENUM) {
|
|
texture.texture->setWrap(texture.clamp, false, false);
|
|
}
|
|
if (texture.filter != GL_INVALID_ENUM) {
|
|
texture.texture->setFilter(texture.filter, false, false);
|
|
}
|
|
|
|
if (texture.textureTransform) {
|
|
glUniformMatrix4fv(fill.program->getUniform("mainTextureTransform"), 1,
|
|
GL_FALSE, &texture.textureTransform->data[0]);
|
|
}
|
|
}
|
|
|
|
// vertex attributes (tex coord, color, alpha)
|
|
if (vertices.attribFlags & VertexAttribFlags::TextureCoord) {
|
|
meshState().enableTexCoordsVertexArray();
|
|
meshState().bindTexCoordsVertexPointer(vertices.texCoord, vertices.stride);
|
|
} else {
|
|
meshState().disableTexCoordsVertexArray();
|
|
}
|
|
int colorLocation = -1;
|
|
if (vertices.attribFlags & VertexAttribFlags::Color) {
|
|
colorLocation = fill.program->getAttrib("colors");
|
|
glEnableVertexAttribArray(colorLocation);
|
|
glVertexAttribPointer(colorLocation, 4, GL_FLOAT, GL_FALSE, vertices.stride, vertices.color);
|
|
}
|
|
int alphaLocation = -1;
|
|
if (vertices.attribFlags & VertexAttribFlags::Alpha) {
|
|
// NOTE: alpha vertex position is computed assuming no VBO
|
|
const void* alphaCoords = ((const GLbyte*) vertices.position) + kVertexAlphaOffset;
|
|
alphaLocation = fill.program->getAttrib("vtxAlpha");
|
|
glEnableVertexAttribArray(alphaLocation);
|
|
glVertexAttribPointer(alphaLocation, 1, GL_FLOAT, GL_FALSE, vertices.stride, alphaCoords);
|
|
}
|
|
// Shader uniforms
|
|
SkiaShader::apply(*mCaches, fill.skiaShaderData, mViewportWidth, mViewportHeight);
|
|
|
|
GL_CHECKPOINT(MODERATE);
|
|
Texture* texture = (fill.skiaShaderData.skiaShaderType & kBitmap_SkiaShaderType) ?
|
|
fill.skiaShaderData.bitmapData.bitmapTexture : nullptr;
|
|
const AutoTexture autoCleanup(texture);
|
|
|
|
// If we have a shader and a base texture, the base texture is assumed to be an alpha mask
|
|
// which means the color space conversion applies to the shader's bitmap
|
|
Texture* colorSpaceTexture = texture != nullptr ? texture : fill.texture.texture;
|
|
if (colorSpaceTexture != nullptr) {
|
|
if (colorSpaceTexture->hasColorSpaceConversion()) {
|
|
const ColorSpaceConnector* connector = colorSpaceTexture->getColorSpaceConnector();
|
|
glUniformMatrix3fv(fill.program->getUniform("colorSpaceMatrix"), 1,
|
|
GL_FALSE, connector->getTransform().asArray());
|
|
}
|
|
|
|
TransferFunctionType transferFunction = colorSpaceTexture->getTransferFunctionType();
|
|
if (transferFunction != TransferFunctionType::None) {
|
|
const ColorSpaceConnector* connector = colorSpaceTexture->getColorSpaceConnector();
|
|
const ColorSpace& source = connector->getSource();
|
|
|
|
switch (transferFunction) {
|
|
case TransferFunctionType::None:
|
|
break;
|
|
case TransferFunctionType::Full:
|
|
glUniform1fv(fill.program->getUniform("transferFunction"), 7,
|
|
reinterpret_cast<const float*>(&source.getTransferParameters().g));
|
|
break;
|
|
case TransferFunctionType::Limited:
|
|
glUniform1fv(fill.program->getUniform("transferFunction"), 5,
|
|
reinterpret_cast<const float*>(&source.getTransferParameters().g));
|
|
break;
|
|
case TransferFunctionType::Gamma:
|
|
glUniform1f(fill.program->getUniform("transferFunctionGamma"),
|
|
source.getTransferParameters().g);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ------------------------------------
|
|
// ---------- GL state setup ----------
|
|
// ------------------------------------
|
|
blend().setFactors(glop.blend.src, glop.blend.dst);
|
|
|
|
GL_CHECKPOINT(MODERATE);
|
|
|
|
// ------------------------------------
|
|
// ---------- Actual drawing ----------
|
|
// ------------------------------------
|
|
if (indices.bufferObject == meshState().getQuadListIBO()) {
|
|
// Since the indexed quad list is of limited length, we loop over
|
|
// the glDrawXXX method while updating the vertex pointer
|
|
GLsizei elementsCount = mesh.elementCount;
|
|
const GLbyte* vertexData = static_cast<const GLbyte*>(vertices.position);
|
|
while (elementsCount > 0) {
|
|
GLsizei drawCount = std::min(elementsCount, (GLsizei) kMaxNumberOfQuads * 6);
|
|
meshState().bindPositionVertexPointer(vertexData, vertices.stride);
|
|
if (vertices.attribFlags & VertexAttribFlags::TextureCoord) {
|
|
meshState().bindTexCoordsVertexPointer(
|
|
vertexData + kMeshTextureOffset, vertices.stride);
|
|
}
|
|
|
|
glDrawElements(mesh.primitiveMode, drawCount, GL_UNSIGNED_SHORT, nullptr);
|
|
elementsCount -= drawCount;
|
|
vertexData += (drawCount / 6) * 4 * vertices.stride;
|
|
}
|
|
} else if (indices.bufferObject || indices.indices) {
|
|
glDrawElements(mesh.primitiveMode, mesh.elementCount, GL_UNSIGNED_SHORT, indices.indices);
|
|
} else {
|
|
glDrawArrays(mesh.primitiveMode, 0, mesh.elementCount);
|
|
}
|
|
|
|
GL_CHECKPOINT(MODERATE);
|
|
|
|
// -----------------------------------
|
|
// ---------- Mesh teardown ----------
|
|
// -----------------------------------
|
|
if (vertices.attribFlags & VertexAttribFlags::Alpha) {
|
|
glDisableVertexAttribArray(alphaLocation);
|
|
}
|
|
if (vertices.attribFlags & VertexAttribFlags::Color) {
|
|
glDisableVertexAttribArray(colorLocation);
|
|
}
|
|
|
|
GL_CHECKPOINT(MODERATE);
|
|
}
|
|
|
|
void RenderState::dump() {
|
|
blend().dump();
|
|
meshState().dump();
|
|
scissor().dump();
|
|
stencil().dump();
|
|
}
|
|
|
|
} /* namespace uirenderer */
|
|
} /* namespace android */
|