bug:34809371 In some applications, the first draw is not opaque - either because the application is misbehaved, or because hwui is not able to reliably tell whether the layer is opaque or translucent. This is undefined behaviour in OpenGL ES and has a significant performance and bandwidth impact on some tiler GPUs as it requires loading the previous frame's color data. This change disables blending in that case and also for effectively opaque blend modes (SRC=GL_ONE, DST=GL_ZERO). It increases performance by ~10% for Leanback CTS on some low-end GPUs (gradient layer that hwui incorrectly believes to be translucent). Test: manual - visual inspection on fugu (nexus player) Change-Id: I2cbf1c76678acae1a36923e72fd18ed55cd89dc2
489 lines
17 KiB
C++
489 lines
17 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();
|
|
|
|
// Deferred because creation needs GL context for texture limits
|
|
if (!mLayerPool) {
|
|
mLayerPool = new OffscreenBufferPool();
|
|
}
|
|
|
|
// 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:
|
|
if (mLayerPool) mLayerPool->clear();
|
|
break;
|
|
}
|
|
if (mCaches) mCaches->flush(mode);
|
|
}
|
|
|
|
void RenderState::onBitmapDestroyed(uint32_t pixelRefId) {
|
|
if (mCaches && mCaches->textureCache.destroyTexture(pixelRefId)) {
|
|
glFlush();
|
|
GL_CHECKPOINT(MODERATE);
|
|
}
|
|
}
|
|
|
|
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,
|
|
bool overrideDisableBlending) {
|
|
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;
|
|
|
|
// add half pixel to round out integer rect space to cover pixel centers
|
|
float roundedOutRadius = state->radius + 0.5f;
|
|
|
|
// Divide by the radius to simplify the calculations in the fragment shader
|
|
// roundRectPos is also passed from vertex shader relative to top/left & radius
|
|
glUniform4f(fill.program->getUniform("roundRectInnerRectLTWH"),
|
|
innerRect.left / roundedOutRadius, innerRect.top / roundedOutRadius,
|
|
(innerRect.right - innerRect.left) / roundedOutRadius,
|
|
(innerRect.bottom - innerRect.top) / roundedOutRadius);
|
|
|
|
glUniformMatrix4fv(fill.program->getUniform("roundRectInvTransform"),
|
|
1, GL_FALSE, &state->matrix.data[0]);
|
|
|
|
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 ----------
|
|
// ------------------------------------
|
|
if (CC_UNLIKELY(overrideDisableBlending)) {
|
|
blend().setFactors(GL_ZERO, GL_ZERO);
|
|
} else {
|
|
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);
|
|
GLsizei vertexCount = (drawCount / 6) * 4;
|
|
meshState().bindPositionVertexPointer(vertexData, vertices.stride);
|
|
if (vertices.attribFlags & VertexAttribFlags::TextureCoord) {
|
|
meshState().bindTexCoordsVertexPointer(
|
|
vertexData + kMeshTextureOffset, vertices.stride);
|
|
}
|
|
|
|
if (mCaches->extensions().getMajorGlVersion() >= 3) {
|
|
glDrawRangeElements(mesh.primitiveMode, 0, vertexCount-1, drawCount, GL_UNSIGNED_SHORT, nullptr);
|
|
} else {
|
|
glDrawElements(mesh.primitiveMode, drawCount, GL_UNSIGNED_SHORT, nullptr);
|
|
}
|
|
elementsCount -= drawCount;
|
|
vertexData += vertexCount * vertices.stride;
|
|
}
|
|
} else if (indices.bufferObject || indices.indices) {
|
|
if (mCaches->extensions().getMajorGlVersion() >= 3) {
|
|
// use glDrawRangeElements to reduce CPU overhead (otherwise the driver has to determine the min/max index values)
|
|
glDrawRangeElements(mesh.primitiveMode, 0, mesh.vertexCount-1, mesh.elementCount, GL_UNSIGNED_SHORT, indices.indices);
|
|
} else {
|
|
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 */
|