mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-01 22:55:23 +00:00
723 lines
24 KiB
C++
723 lines
24 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"
|
|
#include "gfx2DGlue.h"
|
|
#include "ScopedGLHelpers.h"
|
|
|
|
namespace mozilla {
|
|
namespace 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);
|
|
}
|
|
|
|
// Moz2D equivalent...
|
|
already_AddRefed<TextureImage>
|
|
TextureImage::Create(GLContext* gl,
|
|
const gfx::IntSize& size,
|
|
TextureImage::ContentType contentType,
|
|
GLenum wrapMode,
|
|
TextureImage::Flags flags)
|
|
{
|
|
return Create(gl, ThebesIntSize(size), contentType, wrapMode, flags);
|
|
}
|
|
|
|
bool
|
|
TextureImage::UpdateFromDataSource(gfx::DataSourceSurface *aSurface,
|
|
const nsIntRegion* aDestRegion,
|
|
const gfx::IntPoint* aSrcPoint)
|
|
{
|
|
nsIntRegion destRegion = aDestRegion ? *aDestRegion
|
|
: nsIntRect(0, 0,
|
|
aSurface->GetSize().width,
|
|
aSurface->GetSize().height);
|
|
nsIntPoint thebesSrcPoint = aSrcPoint ? nsIntPoint(aSrcPoint->x, aSrcPoint->y)
|
|
: nsIntPoint(0, 0);
|
|
RefPtr<gfxASurface> thebesSurf
|
|
= new gfxImageSurface(aSurface->GetData(),
|
|
ThebesIntSize(aSurface->GetSize()),
|
|
aSurface->Stride(),
|
|
SurfaceFormatToImageFormat(aSurface->GetFormat()));
|
|
return DirectUpdate(thebesSurf, destRegion, thebesSrcPoint);
|
|
}
|
|
|
|
gfx::IntRect TextureImage::GetTileRect() {
|
|
return gfx::IntRect(gfx::IntPoint(0,0), ToIntSize(mSize));
|
|
}
|
|
|
|
gfx::IntRect TextureImage::GetSrcTileRect() {
|
|
return GetTileRect();
|
|
}
|
|
|
|
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 nullptr;
|
|
}
|
|
|
|
ImageFormat format =
|
|
(GetContentType() == GFX_CONTENT_COLOR) ?
|
|
gfxImageFormatRGB24 : gfxImageFormatARGB32;
|
|
mUpdateSurface =
|
|
GetSurfaceForUpdate(gfxIntSize(rgnSize.width, rgnSize.height), format);
|
|
|
|
if (!mUpdateSurface || mUpdateSurface->CairoStatus()) {
|
|
mUpdateSurface = nullptr;
|
|
return nullptr;
|
|
}
|
|
|
|
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();
|
|
|
|
mTextureFormat =
|
|
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;
|
|
}
|
|
|
|
mTextureFormat =
|
|
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,
|
|
nullptr);
|
|
|
|
mTextureState = Allocated;
|
|
mSize = aSize;
|
|
}
|
|
|
|
// Moz2D equivalents...
|
|
void TextureImage::Resize(const gfx::IntSize& aSize) {
|
|
Resize(ThebesIntSize(aSize));
|
|
}
|
|
|
|
gfx::IntSize TextureImage::GetSize() const {
|
|
return ToIntSize(mSize);
|
|
}
|
|
|
|
TextureImage::TextureImage(const gfx::IntSize& aSize,
|
|
GLenum aWrapMode, ContentType aContentType,
|
|
Flags aFlags)
|
|
: mSize(ThebesIntSize(aSize))
|
|
, mWrapMode(aWrapMode)
|
|
, mContentType(aContentType)
|
|
, mFilter(GraphicsFilter::FILTER_GOOD)
|
|
, mFlags(aFlags)
|
|
{}
|
|
|
|
BasicTextureImage::BasicTextureImage(GLuint aTexture,
|
|
const nsIntSize& aSize,
|
|
GLenum aWrapMode,
|
|
ContentType aContentType,
|
|
GLContext* aContext,
|
|
TextureImage::Flags aFlags /* = TextureImage::NoFlags */,
|
|
TextureImage::ImageFormat aImageFormat /* = gfxImageFormatUnknown */)
|
|
: TextureImage(aSize, aWrapMode, aContentType, aFlags, aImageFormat)
|
|
, mTexture(aTexture)
|
|
, mTextureState(Created)
|
|
, mGLContext(aContext)
|
|
, mUpdateOffset(0, 0)
|
|
{
|
|
}
|
|
|
|
BasicTextureImage::BasicTextureImage(GLuint aTexture,
|
|
const gfx::IntSize& aSize,
|
|
GLenum aWrapMode,
|
|
ContentType aContentType,
|
|
GLContext* aContext,
|
|
TextureImage::Flags aFlags,
|
|
TextureImage::ImageFormat aImageFormat)
|
|
: TextureImage(ThebesIntSize(aSize), aWrapMode, aContentType, aFlags, aImageFormat)
|
|
, mTexture(aTexture)
|
|
, mTextureState(Created)
|
|
, mGLContext(aContext)
|
|
, mUpdateOffset(0, 0)
|
|
{}
|
|
|
|
already_AddRefed<TextureImage>
|
|
CreateBasicTextureImage(GLContext* aGL,
|
|
const gfx::IntSize& aSize,
|
|
TextureImage::ContentType aContentType,
|
|
GLenum aWrapMode,
|
|
TextureImage::Flags aFlags)
|
|
{
|
|
return CreateBasicTextureImage(aGL, ThebesIntSize(aSize), aContentType, aWrapMode, aFlags);
|
|
}
|
|
|
|
TiledTextureImage::TiledTextureImage(GLContext* aGL,
|
|
nsIntSize aSize,
|
|
TextureImage::ContentType aContentType,
|
|
TextureImage::Flags aFlags,
|
|
TextureImage::ImageFormat aImageFormat)
|
|
: TextureImage(aSize, LOCAL_GL_CLAMP_TO_EDGE, aContentType, aFlags)
|
|
, mCurrentImage(0)
|
|
, mIterationCallback(nullptr)
|
|
, mInUpdate(false)
|
|
, mRows(0)
|
|
, mColumns(0)
|
|
, mGL(aGL)
|
|
, mTextureState(Created)
|
|
, mImageFormat(aImageFormat)
|
|
{
|
|
if (!(aFlags & TextureImage::DisallowBigImage) && 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 = ThebesIntRect(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;
|
|
|
|
mTextureFormat = mImages[0]->GetTextureFormat();
|
|
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(nsIntPoint(xPos,yPos),
|
|
ThebesIntSize(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),
|
|
ThebesIntSize(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
|
|
gfxImageFormat format =
|
|
(GetContentType() == GFX_CONTENT_COLOR) ?
|
|
gfxImageFormatRGB24 : gfxImageFormatARGB32;
|
|
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;
|
|
mTextureFormat = mImages[mCurrentImage]->GetTextureFormat();
|
|
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),
|
|
ThebesIntSize(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;
|
|
mTextureFormat = mImages[0]->GetTextureFormat();
|
|
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;
|
|
}
|
|
|
|
gfx::IntRect TiledTextureImage::GetTileRect()
|
|
{
|
|
if (!GetTileCount()) {
|
|
return gfx::IntRect();
|
|
}
|
|
gfx::IntRect rect = mImages[mCurrentImage]->GetTileRect();
|
|
unsigned int xPos = (mCurrentImage % mColumns) * mTileSize;
|
|
unsigned int yPos = (mCurrentImage / mColumns) * mTileSize;
|
|
rect.MoveBy(xPos, yPos);
|
|
return rect;
|
|
}
|
|
|
|
gfx::IntRect TiledTextureImage::GetSrcTileRect()
|
|
{
|
|
gfx::IntRect rect = GetTileRect();
|
|
unsigned int srcY = mFlags & NeedsYFlip
|
|
? mSize.height - rect.height - rect.y
|
|
: rect.y;
|
|
return gfx::IntRect(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, mImageFormat);
|
|
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);
|
|
}
|
|
}
|
|
|
|
already_AddRefed<TextureImage>
|
|
CreateBasicTextureImage(GLContext* aGL,
|
|
const nsIntSize& aSize,
|
|
TextureImage::ContentType aContentType,
|
|
GLenum aWrapMode,
|
|
TextureImage::Flags aFlags,
|
|
TextureImage::ImageFormat aImageFormat)
|
|
{
|
|
bool useNearestFilter = aFlags & TextureImage::UseNearestFilter;
|
|
aGL->MakeCurrent();
|
|
|
|
GLuint texture = 0;
|
|
aGL->fGenTextures(1, &texture);
|
|
|
|
ScopedBindTexture bind(aGL, texture);
|
|
|
|
GLint texfilter = useNearestFilter ? LOCAL_GL_NEAREST : LOCAL_GL_LINEAR;
|
|
aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER, texfilter);
|
|
aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER, texfilter);
|
|
aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S, aWrapMode);
|
|
aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T, aWrapMode);
|
|
|
|
nsRefPtr<BasicTextureImage> texImage =
|
|
new BasicTextureImage(texture, aSize, aWrapMode, aContentType,
|
|
aGL, aFlags, aImageFormat);
|
|
return texImage.forget();
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace
|