Romain Guy caaaa66e57 Convert bitmaps to sRGB/scRGB when they have a color profile
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
2017-03-28 18:35:49 -07:00

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 */