53e51e4aa9
bug:20063841 Restores old SkShader matrix behavior from before the Glop refactor. Many drawing operations draw without sending the canvas transform to the GL shader. In such cases, we need to adapt the matrix sent to the SkShader logic to invert the canvas transform that's built into the mesh. Change-Id: I42b6f59df36ce46436322b95bf9ad2140795ee58
356 lines
13 KiB
C++
356 lines
13 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 "renderstate/RenderState.h"
|
|
|
|
#include "renderthread/CanvasContext.h"
|
|
#include "renderthread/EglManager.h"
|
|
#include "utils/GLUtils.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");
|
|
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();
|
|
mCaches->textureCache.setAssetAtlas(&mAssetAtlas);
|
|
}
|
|
|
|
static void layerLostGlContext(Layer* layer) {
|
|
layer->onGlContextLost();
|
|
}
|
|
|
|
void RenderState::onGLContextDestroyed() {
|
|
/*
|
|
size_t size = mActiveLayers.size();
|
|
if (CC_UNLIKELY(size != 0)) {
|
|
ALOGE("Crashing, have %d contexts and %d layers at context destruction. isempty %d",
|
|
mRegisteredContexts.size(), size, mActiveLayers.empty());
|
|
mCaches->dumpMemoryUsage();
|
|
for (std::set<renderthread::CanvasContext*>::iterator cit = mRegisteredContexts.begin();
|
|
cit != mRegisteredContexts.end(); cit++) {
|
|
renderthread::CanvasContext* context = *cit;
|
|
ALOGE("Context: %p (root = %p)", context, context->mRootRenderNode.get());
|
|
ALOGE(" Prefeteched layers: %zu", context->mPrefetechedLayers.size());
|
|
for (std::set<RenderNode*>::iterator pit = context->mPrefetechedLayers.begin();
|
|
pit != context->mPrefetechedLayers.end(); pit++) {
|
|
(*pit)->debugDumpLayers(" ");
|
|
}
|
|
context->mRootRenderNode->debugDumpLayers(" ");
|
|
}
|
|
|
|
|
|
if (mActiveLayers.begin() == mActiveLayers.end()) {
|
|
ALOGE("set has become empty. wat.");
|
|
}
|
|
for (std::set<const Layer*>::iterator lit = mActiveLayers.begin();
|
|
lit != mActiveLayers.end(); lit++) {
|
|
const Layer* layer = *(lit);
|
|
ALOGE("Layer %p, state %d, texlayer %d, fbo %d, buildlayered %d",
|
|
layer, layer->state, layer->isTextureLayer(), layer->getFbo(), layer->wasBuildLayered);
|
|
}
|
|
LOG_ALWAYS_FATAL("%d layers have survived gl context destruction", size);
|
|
}
|
|
*/
|
|
|
|
// TODO: reset all cached state in state objects
|
|
std::for_each(mActiveLayers.begin(), mActiveLayers.end(), layerLostGlContext);
|
|
mAssetAtlas.terminate();
|
|
|
|
mCaches->terminate();
|
|
|
|
delete mBlend;
|
|
mBlend = nullptr;
|
|
delete mMeshState;
|
|
mMeshState = nullptr;
|
|
delete mScissor;
|
|
mScissor = nullptr;
|
|
delete mStencil;
|
|
mStencil = nullptr;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
void RenderState::invokeFunctor(Functor* functor, DrawGlInfo::Mode mode, DrawGlInfo* info) {
|
|
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);
|
|
}
|
|
|
|
void RenderState::resumeFromFunctorInvoke() {
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|
|
void RenderState::requireGLContext() {
|
|
assertOnGLThread();
|
|
LOG_ALWAYS_FATAL_IF(!mRenderThread.eglManager().hasEglContext(),
|
|
"No GL context!");
|
|
}
|
|
|
|
void RenderState::assertOnGLThread() {
|
|
pthread_t curr = pthread_self();
|
|
LOG_ALWAYS_FATAL_IF(!pthread_equal(mThreadId, curr), "Wrong thread!");
|
|
}
|
|
|
|
class DecStrongTask : public renderthread::RenderTask {
|
|
public:
|
|
DecStrongTask(VirtualLightRefBase* object) : mObject(object) {}
|
|
|
|
virtual void run() override {
|
|
mObject->decStrong(nullptr);
|
|
mObject = nullptr;
|
|
delete this;
|
|
}
|
|
|
|
private:
|
|
VirtualLightRefBase* mObject;
|
|
};
|
|
|
|
void RenderState::postDecStrong(VirtualLightRefBase* object) {
|
|
mRenderThread.queue(new DecStrongTask(object));
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Render
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
void RenderState::render(const Glop& glop) {
|
|
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;
|
|
|
|
// ---------------------------------------------
|
|
// ---------- Program + uniform setup ----------
|
|
// ---------------------------------------------
|
|
mCaches->setProgram(fill.program);
|
|
|
|
if (fill.colorEnabled) {
|
|
fill.program->setColor(fill.color);
|
|
}
|
|
|
|
fill.program->set(glop.transform.ortho,
|
|
glop.transform.modelView,
|
|
glop.transform.meshTransform(),
|
|
glop.transform.transformFlags & TransformFlags::OffsetByFudgeFactor);
|
|
|
|
// Color filter uniforms
|
|
if (fill.filterMode == ProgramDescription::kColorBlend) {
|
|
const FloatColor& color = fill.filter.color;
|
|
glUniform4f(mCaches->program().getUniform("colorBlend"),
|
|
color.r, color.g, color.b, color.a);
|
|
} else if (fill.filterMode == ProgramDescription::kColorMatrix) {
|
|
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);
|
|
}
|
|
|
|
// --------------------------------
|
|
// ---------- Mesh setup ----------
|
|
// --------------------------------
|
|
// vertices
|
|
const bool force = meshState().bindMeshBufferInternal(vertices.bufferObject)
|
|
|| (vertices.position != nullptr);
|
|
meshState().bindPositionVertexPointer(force, vertices.position, vertices.stride);
|
|
|
|
// indices
|
|
meshState().bindIndicesBufferInternal(indices.bufferObject);
|
|
|
|
if (vertices.attribFlags & VertexAttribFlags::TextureCoord) {
|
|
const Glop::Fill::TextureData& texture = fill.texture;
|
|
// texture always takes slot 0, shader samplers increment from there
|
|
mCaches->textureState().activateTexture(0);
|
|
|
|
if (texture.clamp != GL_INVALID_ENUM) {
|
|
texture.texture->setWrap(texture.clamp, true, false, texture.target);
|
|
}
|
|
if (texture.filter != GL_INVALID_ENUM) {
|
|
texture.texture->setFilter(texture.filter, true, false, texture.target);
|
|
}
|
|
|
|
mCaches->textureState().bindTexture(texture.target, texture.texture->id);
|
|
meshState().enableTexCoordsVertexArray();
|
|
meshState().bindTexCoordsVertexPointer(force, vertices.texCoord, vertices.stride);
|
|
|
|
if (texture.textureTransform) {
|
|
glUniformMatrix4fv(fill.program->getUniform("mainTextureTransform"), 1,
|
|
GL_FALSE, &texture.textureTransform->data[0]);
|
|
}
|
|
} 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);
|
|
|
|
// ------------------------------------
|
|
// ---------- GL state setup ----------
|
|
// ------------------------------------
|
|
blend().setFactors(glop.blend.src, glop.blend.dst);
|
|
|
|
// ------------------------------------
|
|
// ---------- 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 = MathUtils::min(elementsCount, (GLsizei) kMaxNumberOfQuads * 6);
|
|
|
|
// rebind pointers without forcing, since initial bind handled above
|
|
meshState().bindPositionVertexPointer(false, vertexData, vertices.stride);
|
|
if (vertices.attribFlags & VertexAttribFlags::TextureCoord) {
|
|
meshState().bindTexCoordsVertexPointer(false,
|
|
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);
|
|
}
|
|
|
|
// -----------------------------------
|
|
// ---------- Mesh teardown ----------
|
|
// -----------------------------------
|
|
if (vertices.attribFlags & VertexAttribFlags::Alpha) {
|
|
glDisableVertexAttribArray(alphaLocation);
|
|
}
|
|
if (vertices.attribFlags & VertexAttribFlags::Color) {
|
|
glDisableVertexAttribArray(colorLocation);
|
|
}
|
|
}
|
|
|
|
void RenderState::dump() {
|
|
blend().dump();
|
|
meshState().dump();
|
|
scissor().dump();
|
|
stencil().dump();
|
|
}
|
|
|
|
} /* namespace uirenderer */
|
|
} /* namespace android */
|