gecko/gfx/gl/GLTextureImage.cpp

587 lines
19 KiB
C++

/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40; -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "GLTextureImage.h"
#include "GLContext.h"
#include "gfxContext.h"
#include "gfxPlatform.h"
#include "gfxUtils.h"
using namespace mozilla::gl;
already_AddRefed<TextureImage>
TextureImage::Create(GLContext* gl,
const nsIntSize& size,
TextureImage::ContentType contentType,
GLenum wrapMode,
TextureImage::Flags flags)
{
return gl->CreateTextureImage(size, contentType, wrapMode, flags);
}
BasicTextureImage::~BasicTextureImage()
{
GLContext *ctx = mGLContext;
if (ctx->IsDestroyed() || !ctx->IsOwningThreadCurrent()) {
ctx = ctx->GetSharedContext();
}
// If we have a context, then we need to delete the texture;
// if we don't have a context (either real or shared),
// then they went away when the contex was deleted, because it
// was the only one that had access to it.
if (ctx && !ctx->IsDestroyed()) {
mGLContext->MakeCurrent();
mGLContext->fDeleteTextures(1, &mTexture);
}
}
gfxASurface*
BasicTextureImage::BeginUpdate(nsIntRegion& aRegion)
{
NS_ASSERTION(!mUpdateSurface, "BeginUpdate() without EndUpdate()?");
// determine the region the client will need to repaint
if (mGLContext->CanUploadSubTextures()) {
GetUpdateRegion(aRegion);
} else {
aRegion = nsIntRect(nsIntPoint(0, 0), mSize);
}
mUpdateRegion = aRegion;
nsIntRect rgnSize = mUpdateRegion.GetBounds();
if (!nsIntRect(nsIntPoint(0, 0), mSize).Contains(rgnSize)) {
NS_ERROR("update outside of image");
return NULL;
}
ImageFormat format =
(GetContentType() == gfxASurface::CONTENT_COLOR) ?
gfxASurface::ImageFormatRGB24 : gfxASurface::ImageFormatARGB32;
mUpdateSurface =
GetSurfaceForUpdate(gfxIntSize(rgnSize.width, rgnSize.height), format);
if (!mUpdateSurface || mUpdateSurface->CairoStatus()) {
mUpdateSurface = NULL;
return NULL;
}
mUpdateSurface->SetDeviceOffset(gfxPoint(-rgnSize.x, -rgnSize.y));
return mUpdateSurface;
}
void
BasicTextureImage::GetUpdateRegion(nsIntRegion& aForRegion)
{
// if the texture hasn't been initialized yet, or something important
// changed, we need to recreate our backing surface and force the
// client to paint everything
if (mTextureState != Valid)
aForRegion = nsIntRect(nsIntPoint(0, 0), mSize);
}
void
BasicTextureImage::EndUpdate()
{
NS_ASSERTION(!!mUpdateSurface, "EndUpdate() without BeginUpdate()?");
// FIXME: this is the slow boat. Make me fast (with GLXPixmap?).
// Undo the device offset that BeginUpdate set; doesn't much matter for us here,
// but important if we ever do anything directly with the surface.
mUpdateSurface->SetDeviceOffset(gfxPoint(0, 0));
bool relative = FinishedSurfaceUpdate();
mShaderType =
mGLContext->UploadSurfaceToTexture(mUpdateSurface,
mUpdateRegion,
mTexture,
mTextureState == Created,
mUpdateOffset,
relative);
FinishedSurfaceUpload();
mUpdateSurface = nullptr;
mTextureState = Valid;
}
void
BasicTextureImage::BindTexture(GLenum aTextureUnit)
{
mGLContext->fActiveTexture(aTextureUnit);
mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, mTexture);
mGLContext->fActiveTexture(LOCAL_GL_TEXTURE0);
}
void
BasicTextureImage::ApplyFilter()
{
mGLContext->ApplyFilterToBoundTexture(mFilter);
}
already_AddRefed<gfxASurface>
BasicTextureImage::GetSurfaceForUpdate(const gfxIntSize& aSize, ImageFormat aFmt)
{
return gfxPlatform::GetPlatform()->
CreateOffscreenSurface(aSize, gfxASurface::ContentFromFormat(aFmt));
}
bool
BasicTextureImage::FinishedSurfaceUpdate()
{
return false;
}
void
BasicTextureImage::FinishedSurfaceUpload()
{
}
bool
BasicTextureImage::DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom /* = nsIntPoint(0, 0) */)
{
nsIntRect bounds = aRegion.GetBounds();
nsIntRegion region;
if (mTextureState != Valid) {
bounds = nsIntRect(0, 0, mSize.width, mSize.height);
region = nsIntRegion(bounds);
} else {
region = aRegion;
}
mShaderType =
mGLContext->UploadSurfaceToTexture(aSurf,
region,
mTexture,
mTextureState == Created,
bounds.TopLeft() + aFrom,
false);
mTextureState = Valid;
return true;
}
void
BasicTextureImage::Resize(const nsIntSize& aSize)
{
NS_ASSERTION(!mUpdateSurface, "Resize() while in update?");
mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, mTexture);
mGLContext->fTexImage2D(LOCAL_GL_TEXTURE_2D,
0,
LOCAL_GL_RGBA,
aSize.width,
aSize.height,
0,
LOCAL_GL_RGBA,
LOCAL_GL_UNSIGNED_BYTE,
NULL);
mTextureState = Allocated;
mSize = aSize;
}
TiledTextureImage::TiledTextureImage(GLContext* aGL,
nsIntSize aSize,
TextureImage::ContentType aContentType,
TextureImage::Flags aFlags)
: TextureImage(aSize, LOCAL_GL_CLAMP_TO_EDGE, aContentType, aFlags)
, mCurrentImage(0)
, mIterationCallback(nullptr)
, mInUpdate(false)
, mRows(0)
, mColumns(0)
, mGL(aGL)
, mTextureState(Created)
{
if (!(aFlags & TextureImage::ForceSingleTile) && mGL->WantsSmallTiles()) {
mTileSize = 256;
} else {
mGL->fGetIntegerv(LOCAL_GL_MAX_TEXTURE_SIZE, (GLint*) &mTileSize);
}
if (aSize.width != 0 && aSize.height != 0) {
Resize(aSize);
}
}
TiledTextureImage::~TiledTextureImage()
{
}
bool
TiledTextureImage::DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom /* = nsIntPoint(0, 0) */)
{
if (mSize.width == 0 || mSize.height == 0) {
return true;
}
nsIntRegion region;
if (mTextureState != Valid) {
nsIntRect bounds = nsIntRect(0, 0, mSize.width, mSize.height);
region = nsIntRegion(bounds);
} else {
region = aRegion;
}
bool result = true;
int oldCurrentImage = mCurrentImage;
BeginTileIteration();
do {
nsIntRect tileRect = GetSrcTileRect();
int xPos = tileRect.x;
int yPos = tileRect.y;
nsIntRegion tileRegion;
tileRegion.And(region, tileRect); // intersect with tile
if (tileRegion.IsEmpty())
continue;
if (mGL->CanUploadSubTextures()) {
tileRegion.MoveBy(-xPos, -yPos); // translate into tile local space
} else {
// If sub-textures are unsupported, expand to tile boundaries
tileRect.x = tileRect.y = 0;
tileRegion = nsIntRegion(tileRect);
}
result &= mImages[mCurrentImage]->
DirectUpdate(aSurf, tileRegion, aFrom + nsIntPoint(xPos, yPos));
if (mCurrentImage == mImages.Length() - 1) {
// We know we're done, but we still need to ensure that the callback
// gets called (e.g. to update the uploaded region).
NextTile();
break;
}
// Override a callback cancelling iteration if the texture wasn't valid.
// We need to force the update in that situation, or we may end up
// showing invalid/out-of-date texture data.
} while (NextTile() || (mTextureState != Valid));
mCurrentImage = oldCurrentImage;
mShaderType = mImages[0]->GetShaderProgramType();
mTextureState = Valid;
return result;
}
void
TiledTextureImage::GetUpdateRegion(nsIntRegion& aForRegion)
{
if (mTextureState != Valid) {
// if the texture hasn't been initialized yet, or something important
// changed, we need to recreate our backing surface and force the
// client to paint everything
aForRegion = nsIntRect(nsIntPoint(0, 0), mSize);
return;
}
nsIntRegion newRegion;
// We need to query each texture with the region it will be drawing and
// set aForRegion to be the combination of all of these regions
for (unsigned i = 0; i < mImages.Length(); i++) {
int xPos = (i % mColumns) * mTileSize;
int yPos = (i / mColumns) * mTileSize;
nsIntRect imageRect = nsIntRect(nsIntRect(nsIntPoint(xPos,yPos), mImages[i]->GetSize()));
if (aForRegion.Intersects(imageRect)) {
// Make a copy of the region
nsIntRegion subRegion;
subRegion.And(aForRegion, imageRect);
// Translate it into tile-space
subRegion.MoveBy(-xPos, -yPos);
// Query region
mImages[i]->GetUpdateRegion(subRegion);
// Translate back
subRegion.MoveBy(xPos, yPos);
// Add to the accumulated region
newRegion.Or(newRegion, subRegion);
}
}
aForRegion = newRegion;
}
gfxASurface*
TiledTextureImage::BeginUpdate(nsIntRegion& aRegion)
{
NS_ASSERTION(!mInUpdate, "nested update");
mInUpdate = true;
// Note, we don't call GetUpdateRegion here as if the updated region is
// fully contained in a single tile, we get to avoid iterating through
// the tiles again (and a little copying).
if (mTextureState != Valid)
{
// if the texture hasn't been initialized yet, or something important
// changed, we need to recreate our backing surface and force the
// client to paint everything
aRegion = nsIntRect(nsIntPoint(0, 0), mSize);
}
nsIntRect bounds = aRegion.GetBounds();
for (unsigned i = 0; i < mImages.Length(); i++) {
int xPos = (i % mColumns) * mTileSize;
int yPos = (i / mColumns) * mTileSize;
nsIntRegion imageRegion = nsIntRegion(nsIntRect(nsIntPoint(xPos,yPos), mImages[i]->GetSize()));
// a single Image can handle this update request
if (imageRegion.Contains(aRegion)) {
// adjust for tile offset
aRegion.MoveBy(-xPos, -yPos);
// forward the actual call
nsRefPtr<gfxASurface> surface = mImages[i]->BeginUpdate(aRegion);
// caller expects container space
aRegion.MoveBy(xPos, yPos);
// Correct the device offset
gfxPoint offset = surface->GetDeviceOffset();
surface->SetDeviceOffset(gfxPoint(offset.x - xPos,
offset.y - yPos));
// we don't have a temp surface
mUpdateSurface = nullptr;
// remember which image to EndUpdate
mCurrentImage = i;
return surface.get();
}
}
// Get the real updated region, taking into account the capabilities of
// each TextureImage tile
GetUpdateRegion(aRegion);
mUpdateRegion = aRegion;
bounds = aRegion.GetBounds();
// update covers multiple Images - create a temp surface to paint in
gfxASurface::gfxImageFormat format =
(GetContentType() == gfxASurface::CONTENT_COLOR) ?
gfxASurface::ImageFormatRGB24 : gfxASurface::ImageFormatARGB32;
mUpdateSurface = gfxPlatform::GetPlatform()->
CreateOffscreenSurface(gfxIntSize(bounds.width, bounds.height), gfxASurface::ContentFromFormat(format));
mUpdateSurface->SetDeviceOffset(gfxPoint(-bounds.x, -bounds.y));
return mUpdateSurface;
}
void
TiledTextureImage::EndUpdate()
{
NS_ASSERTION(mInUpdate, "EndUpdate not in update");
if (!mUpdateSurface) { // update was to a single TextureImage
mImages[mCurrentImage]->EndUpdate();
mInUpdate = false;
mTextureState = Valid;
mShaderType = mImages[mCurrentImage]->GetShaderProgramType();
return;
}
// upload tiles from temp surface
for (unsigned i = 0; i < mImages.Length(); i++) {
int xPos = (i % mColumns) * mTileSize;
int yPos = (i / mColumns) * mTileSize;
nsIntRect imageRect = nsIntRect(nsIntPoint(xPos,yPos), mImages[i]->GetSize());
nsIntRegion subregion;
subregion.And(mUpdateRegion, imageRect);
if (subregion.IsEmpty())
continue;
subregion.MoveBy(-xPos, -yPos); // Tile-local space
// copy tile from temp surface
gfxASurface* surf = mImages[i]->BeginUpdate(subregion);
nsRefPtr<gfxContext> ctx = new gfxContext(surf);
gfxUtils::ClipToRegion(ctx, subregion);
ctx->SetOperator(gfxContext::OPERATOR_SOURCE);
ctx->SetSource(mUpdateSurface, gfxPoint(-xPos, -yPos));
ctx->Paint();
mImages[i]->EndUpdate();
}
mUpdateSurface = nullptr;
mInUpdate = false;
mShaderType = mImages[0]->GetShaderProgramType();
mTextureState = Valid;
}
void TiledTextureImage::BeginTileIteration()
{
mCurrentImage = 0;
}
bool TiledTextureImage::NextTile()
{
bool continueIteration = true;
if (mIterationCallback)
continueIteration = mIterationCallback(this, mCurrentImage,
mIterationCallbackData);
if (mCurrentImage + 1 < mImages.Length()) {
mCurrentImage++;
return continueIteration;
}
return false;
}
void TiledTextureImage::SetIterationCallback(TileIterationCallback aCallback,
void* aCallbackData)
{
mIterationCallback = aCallback;
mIterationCallbackData = aCallbackData;
}
nsIntRect TiledTextureImage::GetTileRect()
{
if (!GetTileCount()) {
return nsIntRect();
}
nsIntRect rect = mImages[mCurrentImage]->GetTileRect();
unsigned int xPos = (mCurrentImage % mColumns) * mTileSize;
unsigned int yPos = (mCurrentImage / mColumns) * mTileSize;
rect.MoveBy(xPos, yPos);
return rect;
}
nsIntRect TiledTextureImage::GetSrcTileRect()
{
nsIntRect rect = GetTileRect();
unsigned int srcY = mFlags & NeedsYFlip
? mSize.height - rect.height - rect.y
: rect.y;
return nsIntRect(rect.x, srcY, rect.width, rect.height);
}
void
TiledTextureImage::BindTexture(GLenum aTextureUnit)
{
if (!GetTileCount()) {
return;
}
mImages[mCurrentImage]->BindTexture(aTextureUnit);
}
void
TiledTextureImage::ApplyFilter()
{
mGL->ApplyFilterToBoundTexture(mFilter);
}
/*
* Resize, trying to reuse tiles. The reuse strategy is to decide on reuse per
* column. A tile on a column is reused if it hasn't changed size, otherwise it
* is discarded/replaced. Extra tiles on a column are pruned after iterating
* each column, and extra rows are pruned after iteration over the entire image
* finishes.
*/
void TiledTextureImage::Resize(const nsIntSize& aSize)
{
if (mSize == aSize && mTextureState != Created) {
return;
}
// calculate rows and columns, rounding up
unsigned int columns = (aSize.width + mTileSize - 1) / mTileSize;
unsigned int rows = (aSize.height + mTileSize - 1) / mTileSize;
// Iterate over old tile-store and insert/remove tiles as necessary
int row;
unsigned int i = 0;
for (row = 0; row < (int)rows; row++) {
// If we've gone beyond how many rows there were before, set mColumns to
// zero so that we only create new tiles.
if (row >= (int)mRows)
mColumns = 0;
// Similarly, if we're on the last row of old tiles and the height has
// changed, discard all tiles in that row.
// This will cause the pruning of columns not to work, but we don't need
// to worry about that, as no more tiles will be reused past this point
// anyway.
if ((row == (int)mRows - 1) && (aSize.height != mSize.height))
mColumns = 0;
int col;
for (col = 0; col < (int)columns; col++) {
nsIntSize size( // use tilesize first, then the remainder
(col+1) * mTileSize > (unsigned int)aSize.width ? aSize.width % mTileSize : mTileSize,
(row+1) * mTileSize > (unsigned int)aSize.height ? aSize.height % mTileSize : mTileSize);
bool replace = false;
// Check if we can re-use old tiles.
if (col < (int)mColumns) {
// Reuse an existing tile. If the tile is an end-tile and the
// width differs, replace it instead.
if (mSize.width != aSize.width) {
if (col == (int)mColumns - 1) {
// Tile at the end of the old column, replace it with
// a new one.
replace = true;
} else if (col == (int)columns - 1) {
// Tile at the end of the new column, create a new one.
} else {
// Before the last column on both the old and new sizes,
// reuse existing tile.
i++;
continue;
}
} else {
// Width hasn't changed, reuse existing tile.
i++;
continue;
}
}
// Create a new tile.
nsRefPtr<TextureImage> teximg =
mGL->TileGenFunc(size, mContentType, mFlags);
if (replace)
mImages.ReplaceElementAt(i, teximg.forget());
else
mImages.InsertElementAt(i, teximg.forget());
i++;
}
// Prune any unused tiles on the end of the column.
if (row < (int)mRows) {
for (col = (int)mColumns - col; col > 0; col--) {
mImages.RemoveElementAt(i);
}
}
}
// Prune any unused tiles at the end of the store.
unsigned int length = mImages.Length();
for (; i < length; i++)
mImages.RemoveElementAt(mImages.Length()-1);
// Reset tile-store properties.
mRows = rows;
mColumns = columns;
mSize = aSize;
mTextureState = Allocated;
mCurrentImage = 0;
}
uint32_t TiledTextureImage::GetTileCount()
{
return mImages.Length();
}
TextureImage::ScopedBindTexture::ScopedBindTexture(TextureImage* aTexture,
GLenum aTextureUnit)
: mTexture(aTexture)
{
if (mTexture) {
MOZ_ASSERT(aTextureUnit >= LOCAL_GL_TEXTURE0);
mTexture->BindTexture(aTextureUnit);
}
}