Merge pull request #98 from WaywardHeart/transparent-blitting

Enable transparent blitting from megatextures.
This commit is contained in:
Splendide Imaginarius 2024-04-29 12:55:43 +00:00 committed by GitHub
commit e929cfb7c7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 241 additions and 254 deletions

View file

@ -621,7 +621,7 @@ Bitmap::Bitmap(const char *filename)
SDL_Surface *imgSurf = handler.surface;
initFromSurface(imgSurf, hiresBitmap, true);
initFromSurface(imgSurf, hiresBitmap, false);
}
Bitmap::Bitmap(int width, int height, bool isHires)
@ -789,7 +789,7 @@ Bitmap::Bitmap(TEXFBO &other)
p->addTaintedArea(rect());
}
Bitmap::Bitmap(SDL_Surface *imgSurf, SDL_Surface *imgSurfHires)
Bitmap::Bitmap(SDL_Surface *imgSurf, SDL_Surface *imgSurfHires, bool forceMega)
{
Bitmap *hiresBitmap = nullptr;
@ -799,7 +799,7 @@ Bitmap::Bitmap(SDL_Surface *imgSurf, SDL_Surface *imgSurfHires)
hiresBitmap->setLores(this);
}
initFromSurface(imgSurf, hiresBitmap, false);
initFromSurface(imgSurf, hiresBitmap, forceMega);
}
Bitmap::~Bitmap()
@ -807,18 +807,14 @@ Bitmap::~Bitmap()
dispose();
}
void Bitmap::initFromSurface(SDL_Surface *imgSurf, Bitmap *hiresBitmap, bool freeSurface)
void Bitmap::initFromSurface(SDL_Surface *imgSurf, Bitmap *hiresBitmap, bool forceMega)
{
p->ensureFormat(imgSurf, SDL_PIXELFORMAT_ABGR8888);
if (imgSurf->w > glState.caps.maxTexSize || imgSurf->h > glState.caps.maxTexSize)
if (imgSurf->w > glState.caps.maxTexSize || imgSurf->h > glState.caps.maxTexSize || forceMega)
{
/* Mega surface */
if(!freeSurface) {
throw Exception(Exception::RGSSError, "Cloning Mega Bitmap from Surface not supported");
}
p = new BitmapPrivate(this);
p->selfHires = hiresBitmap;
p->megaSurface = imgSurf;
@ -848,10 +844,6 @@ void Bitmap::initFromSurface(SDL_Surface *imgSurf, Bitmap *hiresBitmap, bool fre
TEX::bind(p->gl.tex);
TEX::uploadImage(p->gl.width, p->gl.height, imgSurf->pixels, GL_RGBA);
if (freeSurface) {
SDL_FreeSurface(imgSurf);
}
}
p->addTaintedArea(rect());
@ -933,7 +925,7 @@ void Bitmap::blt(int x, int y,
if (source.isDisposed())
return;
stretchBlt(IntRect(x, y, rect.w, rect.h),
stretchBlt(IntRect(x, y, abs(rect.w), abs(rect.h)),
source, rect, opacity);
}
@ -1015,7 +1007,7 @@ static bool shrinkRects(int &sourcePos, int &sourceLen, const int &sBitmapLen,
void Bitmap::stretchBlt(IntRect destRect,
const Bitmap &source, IntRect sourceRect,
int opacity)
int opacity, bool smooth)
{
guardDisposed();
@ -1058,129 +1050,235 @@ void Bitmap::stretchBlt(IntRect destRect,
return;
SDL_Surface *srcSurf = source.megaSurface();
SDL_Surface *blitTemp = 0;
bool touchesTaintedArea = p->touchesTaintedArea(destRect);
bool unpack_subimage = srcSurf && gl.unpack_subimage;
if (srcSurf && shState->config().subImageFix)
{
/* Blit from software surface, for broken GL drivers */
Vec2i gpTexSize;
shState->ensureTexSize(sourceRect.w, sourceRect.h, gpTexSize);
shState->bindTex();
GLMeta::subRectImageUpload(srcSurf->w, sourceRect.x, sourceRect.y, 0, 0,
sourceRect.w, sourceRect.h, srcSurf, GL_RGBA);
GLMeta::subRectImageEnd();
SimpleShader &shader = shState->shaders().simple;
shader.bind();
shader.setTranslation(Vec2i());
shader.setTexSize(gpTexSize);
p->pushSetViewport(shader);
p->bindFBO();
Quad &quad = shState->gpQuad();
quad.setTexRect(FloatRect(0, 0, sourceRect.w, sourceRect.h));
quad.setPosRect(destRect);
p->blitQuad(quad);
p->popViewport();
p->addTaintedArea(destRect);
p->onModified();
return;
}
else if (srcSurf)
{
/* Blit from software surface */
/* Don't do transparent blits for now */
if (opacity < 255)
source.ensureNonMega();
SDL_Rect srcRect = sourceRect;
SDL_Rect dstRect = destRect;
SDL_Rect btmRect = { 0, 0, width(), height() };
SDL_Rect bltRect;
if (SDL_IntersectRect(&btmRect, &dstRect, &bltRect) != SDL_TRUE)
return;
int bpp;
Uint32 rMask, gMask, bMask, aMask;
SDL_PixelFormatEnumToMasks(SDL_PIXELFORMAT_ABGR8888,
&bpp, &rMask, &gMask, &bMask, &aMask);
SDL_Surface *blitTemp =
SDL_CreateRGBSurface(0, destRect.w, destRect.h, bpp, rMask, gMask, bMask, aMask);
SDL_BlitScaled(srcSurf, &srcRect, blitTemp, 0);
TEX::bind(getGLTypes().tex);
if (bltRect.w == dstRect.w && bltRect.h == dstRect.h)
{
/* Dest rectangle lies within bounding box */
TEX::uploadSubImage(destRect.x, destRect.y,
destRect.w, destRect.h,
blitTemp->pixels, GL_RGBA);
}
else
{
/* Clipped blit */
GLMeta::subRectImageUpload(blitTemp->w, bltRect.x - dstRect.x, bltRect.y - dstRect.y,
bltRect.x, bltRect.y, bltRect.w, bltRect.h, blitTemp, GL_RGBA);
GLMeta::subRectImageEnd();
}
SDL_FreeSurface(blitTemp);
p->onModified();
return;
}
if (opacity == 255 && !p->touchesTaintedArea(destRect))
if (!srcSurf && opacity == 255 && !touchesTaintedArea)
{
/* Fast blit */
GLMeta::blitBegin(getGLTypes());
GLMeta::blitSource(source.getGLTypes());
GLMeta::blitRectangle(sourceRect, destRect);
GLMeta::blitRectangle(sourceRect, destRect, smooth);
GLMeta::blitEnd();
}
else
{
/* Fragment pipeline */
float normOpacity = (float) opacity / 255.0f;
TEXFBO &gpTex = shState->gpTexFBO(destRect.w, destRect.h);
GLMeta::blitBegin(gpTex);
GLMeta::blitSource(getGLTypes());
GLMeta::blitRectangle(destRect, Vec2i());
GLMeta::blitEnd();
FloatRect bltSubRect((float) sourceRect.x / source.width(),
(float) sourceRect.y / source.height(),
((float) source.width() / sourceRect.w) * ((float) destRect.w / gpTex.width),
((float) source.height() / sourceRect.h) * ((float) destRect.h / gpTex.height));
BltShader &shader = shState->shaders().blt;
shader.bind();
shader.setDestination(gpTex.tex);
shader.setSubRect(bltSubRect);
shader.setOpacity(normOpacity);
Quad &quad = shState->gpQuad();
quad.setTexPosRect(sourceRect, destRect);
quad.setColor(Vec4(1, 1, 1, normOpacity));
source.p->bindTexture(shader, false);
p->bindFBO();
p->pushSetViewport(shader);
p->blitQuad(quad);
p->popViewport();
if (srcSurf)
{
SDL_Rect srcRect = sourceRect;
bool subImageFix = shState->config().subImageFix;
bool srcRectTooBig = srcRect.w > glState.caps.maxTexSize ||
srcRect.h > glState.caps.maxTexSize;
bool srcSurfTooBig = !unpack_subimage && (
srcSurf->w > glState.caps.maxTexSize ||
srcSurf->h > glState.caps.maxTexSize
);
if (srcRectTooBig || srcSurfTooBig)
{
int error;
if (srcRectTooBig)
{
/* We have to resize it here anyway, so use software resizing */
blitTemp =
SDL_CreateRGBSurface(0, abs(destRect.w), abs(destRect.h), p->format->BitsPerPixel,
p->format->Rmask, p->format->Gmask,
p->format->Bmask, p->format->Amask);
if (!blitTemp)
throw Exception(Exception::SDLError, "Error creating temporary surface for blitting: %s",
SDL_GetError());
if (smooth)
{
error = SDL_SoftStretchLinear(srcSurf, &srcRect, blitTemp, 0);
smooth = false;
}
else
{
SDL_Rect tmpRect = {0, 0, blitTemp->w, blitTemp->h};
error = SDL_LowerBlitScaled(srcSurf, &srcRect, blitTemp, &tmpRect);
}
unpack_subimage = false;
}
else
{
/* Just crop it, let the shader resize it later */
blitTemp =
SDL_CreateRGBSurface(0, sourceRect.w, sourceRect.h, p->format->BitsPerPixel,
p->format->Rmask, p->format->Gmask,
p->format->Bmask, p->format->Amask);
if (!blitTemp)
throw Exception(Exception::SDLError, "Error creating temporary surface for blitting: %s",
SDL_GetError());
SDL_Rect tmpRect = {0, 0, blitTemp->w, blitTemp->h};
error = SDL_LowerBlit(srcSurf, &srcRect, blitTemp, &tmpRect);
}
if (error)
{
SDL_FreeSurface(blitTemp);
throw Exception(Exception::SDLError, "Failed to blit surface: %s", SDL_GetError());
}
srcSurf = blitTemp;
sourceRect.w = srcSurf->w;
sourceRect.h = srcSurf->h;
sourceRect.x = 0;
sourceRect.y = 0;
}
if (opacity == 255 && !touchesTaintedArea)
{
if (!subImageFix &&
sourceRect.w == destRect.w && sourceRect.h == destRect.h &&
(unpack_subimage || (srcSurf->w == sourceRect.w && srcSurf->h == sourceRect.h))
)
{
/* No scaling needed */
TEX::bind(getGLTypes().tex);
if (unpack_subimage)
{
gl.PixelStorei(GL_UNPACK_ROW_LENGTH, srcSurf->w);
gl.PixelStorei(GL_UNPACK_SKIP_PIXELS, sourceRect.x);
gl.PixelStorei(GL_UNPACK_SKIP_ROWS, sourceRect.y);
}
TEX::uploadSubImage(destRect.x, destRect.y,
destRect.w, destRect.h,
srcSurf->pixels, GL_RGBA);
if (unpack_subimage)
GLMeta::subRectImageEnd();
}
else
{
/* Resizing or subImageFix involved: need to use intermediary TexFBO */
TEXFBO *gpTF;
if (unpack_subimage)
gpTF = &shState->gpTexFBO(sourceRect.w, sourceRect.h);
else
gpTF = &shState->gpTexFBO(srcSurf->w, srcSurf->h);
TEX::bind(gpTF->tex);
if (unpack_subimage)
{
gl.PixelStorei(GL_UNPACK_ROW_LENGTH, srcSurf->w);
gl.PixelStorei(GL_UNPACK_SKIP_PIXELS, sourceRect.x);
gl.PixelStorei(GL_UNPACK_SKIP_ROWS, sourceRect.y);
sourceRect.x = 0;
sourceRect.y = 0;
TEX::uploadSubImage(0, 0, sourceRect.w, sourceRect.h, srcSurf->pixels, GL_RGBA);
GLMeta::subRectImageEnd();
}
else
{
TEX::uploadSubImage(0, 0, srcSurf->w, srcSurf->h, srcSurf->pixels, GL_RGBA);
}
GLMeta::blitBegin(getGLTypes());
GLMeta::blitSource(*gpTF);
GLMeta::blitRectangle(sourceRect, destRect, smooth);
GLMeta::blitEnd();
}
}
}
if (opacity < 255 || touchesTaintedArea)
{
/* We're touching a tainted area or still need to reduce opacity */
/* Fragment pipeline */
float normOpacity = (float) opacity / 255.0f;
TEXFBO &gpTex = shState->gpTexFBO(abs(destRect.w), abs(destRect.h));
Vec2i gpTexSize;
GLMeta::blitBegin(gpTex);
GLMeta::blitSource(getGLTypes());
GLMeta::blitRectangle(destRect, IntRect(0, 0, abs(destRect.w), abs(destRect.h)));
GLMeta::blitEnd();
int sourceWidth, sourceHeight;
FloatRect bltSubRect;
if (srcSurf)
{
if (unpack_subimage)
{
shState->ensureTexSize(sourceRect.w, sourceRect.h, gpTexSize);
}
else
{
shState->ensureTexSize(srcSurf->w, srcSurf->h, gpTexSize);
}
sourceWidth = gpTexSize.x;
sourceHeight = gpTexSize.y;
shState->bindTex();
if (unpack_subimage)
{
gl.PixelStorei(GL_UNPACK_ROW_LENGTH, srcSurf->w);
gl.PixelStorei(GL_UNPACK_SKIP_PIXELS, sourceRect.x);
gl.PixelStorei(GL_UNPACK_SKIP_ROWS, sourceRect.y);
sourceRect.x = 0;
sourceRect.y = 0;
TEX::uploadSubImage(0, 0, sourceRect.w, sourceRect.h, srcSurf->pixels, GL_RGBA);
GLMeta::subRectImageEnd();
}
else
{
TEX::uploadSubImage(0, 0, srcSurf->w, srcSurf->h, srcSurf->pixels, GL_RGBA);
}
}
else
{
sourceWidth = source.width();
sourceHeight = source.height();
}
bltSubRect = FloatRect((float) sourceRect.x / sourceWidth,
(float) sourceRect.y / sourceHeight,
((float) sourceWidth / sourceRect.w) * ((float) abs(destRect.w) / gpTex.width),
((float) sourceHeight / sourceRect.h) * ((float) abs(destRect.h) / gpTex.height));
BltShader &shader = shState->shaders().blt;
shader.bind();
if (srcSurf)
{
shader.setTexSize(gpTexSize);
}
else
{
source.p->bindTexture(shader, false);
}
shader.setSource();
shader.setDestination(gpTex.tex);
shader.setSubRect(bltSubRect);
shader.setOpacity(normOpacity);
Quad &quad = shState->gpQuad();
quad.setTexPosRect(sourceRect, destRect);
quad.setColor(Vec4(1, 1, 1, normOpacity));
p->bindFBO();
p->pushSetViewport(shader);
if (smooth)
TEX::setSmooth(true);
p->blitQuad(quad);
p->popViewport();
if (smooth)
TEX::setSmooth(false);
}
}
if (blitTemp)
SDL_FreeSurface(blitTemp);
p->addTaintedArea(destRect);
p->onModified();
}
@ -1902,6 +2000,8 @@ void Bitmap::drawText(const IntRect &rect, const char *str, int align)
int rectHeight = rect.h * p->selfHires->height() / height();
p->selfHires->drawText(IntRect(rectX, rectY, rectWidth, rectHeight), str, align);
return;
}
std::string fixed = fixupString(str);
@ -1920,8 +2020,6 @@ void Bitmap::drawText(const IntRect &rect, const char *str, int align)
SDL_Color c = fontColor.toSDLColor();
c.a = 255;
float txtAlpha = fontColor.norm.w;
SDL_Surface *txtSurf;
if (p->font->isSolid())
@ -1993,131 +2091,20 @@ void Bitmap::drawText(const IntRect &rect, const char *str, int align)
if (squeeze > 1)
squeeze = 1;
FloatRect posRect(alignX, alignY, txtSurf->w * squeeze, txtSurf->h);
IntRect destRect(alignX, alignY, 0, 0);
destRect.w = std::min(rect.w, (int)(txtSurf->w * squeeze));
destRect.h = std::min(rect.h, txtSurf->h);
Vec2i gpTexSize;
shState->ensureTexSize(txtSurf->w, txtSurf->h, gpTexSize);
destRect.w = std::min(destRect.w, width() - destRect.x);
destRect.h = std::min(destRect.h, height() - destRect.y);
bool fastBlit = !p->touchesTaintedArea(posRect) && txtAlpha == 1.0f;
IntRect sourceRect;
sourceRect.w = destRect.w / squeeze;
sourceRect.h = destRect.h;
if (fastBlit)
{
if (squeeze == 1.0f && !shState->config().subImageFix)
{
/* Even faster: upload directly to bitmap texture.
* We have to make sure the posRect lies within the texture
* boundaries or texSubImage will generate errors.
* If it partly lies outside bounds we have to upload
* the clipped visible part of it. */
SDL_Rect btmRect;
btmRect.x = btmRect.y = 0;
btmRect.w = width();
btmRect.h = height();
SDL_Rect txtRect;
txtRect.x = posRect.x;
txtRect.y = posRect.y;
txtRect.w = posRect.w;
txtRect.h = posRect.h;
SDL_Rect inters;
/* If we have no intersection at all,
* there's nothing to upload to begin with */
if (SDL_IntersectRect(&btmRect, &txtRect, &inters))
{
bool subImage = false;
int subSrcX = 0, subSrcY = 0;
if (inters.w != txtRect.w || inters.h != txtRect.h)
{
/* Clip the text surface */
subSrcX = inters.x - txtRect.x;
subSrcY = inters.y - txtRect.y;
subImage = true;
posRect.x = inters.x;
posRect.y = inters.y;
posRect.w = inters.w;
posRect.h = inters.h;
}
TEX::bind(p->gl.tex);
if (!subImage)
{
TEX::uploadSubImage(posRect.x, posRect.y,
posRect.w, posRect.h,
txtSurf->pixels, GL_RGBA);
}
else
{
GLMeta::subRectImageUpload(txtSurf->w, subSrcX, subSrcY,
posRect.x, posRect.y,
posRect.w, posRect.h,
txtSurf, GL_RGBA);
GLMeta::subRectImageEnd();
}
}
}
else
{
/* Squeezing involved: need to use intermediary TexFBO */
TEXFBO &gpTF = shState->gpTexFBO(txtSurf->w, txtSurf->h);
TEX::bind(gpTF.tex);
TEX::uploadSubImage(0, 0, txtSurf->w, txtSurf->h, txtSurf->pixels, GL_RGBA);
GLMeta::blitBegin(p->gl);
GLMeta::blitSource(gpTF);
GLMeta::blitRectangle(IntRect(0, 0, txtSurf->w, txtSurf->h),
posRect, true);
GLMeta::blitEnd();
}
}
else
{
/* Aquire a partial copy of the destination
* buffer we're about to render to */
TEXFBO &gpTex2 = shState->gpTexFBO(posRect.w, posRect.h);
GLMeta::blitBegin(gpTex2);
GLMeta::blitSource(p->gl);
GLMeta::blitRectangle(posRect, Vec2i());
GLMeta::blitEnd();
FloatRect bltRect(0, 0,
(float) (gpTexSize.x * squeeze) / gpTex2.width,
(float) gpTexSize.y / gpTex2.height);
BltShader &shader = shState->shaders().blt;
shader.bind();
shader.setTexSize(gpTexSize);
shader.setSource();
shader.setDestination(gpTex2.tex);
shader.setSubRect(bltRect);
shader.setOpacity(txtAlpha);
shState->bindTex();
TEX::uploadSubImage(0, 0, txtSurf->w, txtSurf->h, txtSurf->pixels, GL_RGBA);
TEX::setSmooth(true);
Quad &quad = shState->gpQuad();
quad.setTexRect(FloatRect(0, 0, txtSurf->w, txtSurf->h));
quad.setPosRect(posRect);
p->bindFBO();
p->pushSetViewport(shader);
p->blitQuad(quad);
p->popViewport();
}
SDL_FreeSurface(txtSurf);
p->addTaintedArea(posRect);
p->onModified();
Bitmap txtBitmap(txtSurf, nullptr, true);
bool smooth = squeeze != 1.0f;
stretchBlt(destRect, txtBitmap, sourceRect, fontColor.alpha, smooth);
}
/* http://www.lemoda.net/c/utf8-to-ucs2/index.html */

View file

@ -42,7 +42,7 @@ public:
Bitmap(int width, int height, bool isHires = false);
Bitmap(void *pixeldata, int width, int height);
Bitmap(TEXFBO &other);
Bitmap(SDL_Surface *imgSurf, SDL_Surface *imgSurfHires);
Bitmap(SDL_Surface *imgSurf, SDL_Surface *imgSurfHires, bool forceMega = false);
/* Clone constructor */
@ -50,7 +50,7 @@ public:
Bitmap(const Bitmap &other, int frame = -2);
~Bitmap();
void initFromSurface(SDL_Surface *imgSurf, Bitmap *hiresBitmap, bool freeSurface);
void initFromSurface(SDL_Surface *imgSurf, Bitmap *hiresBitmap, bool forceMega = false);
int width() const;
int height() const;
@ -68,7 +68,7 @@ public:
void stretchBlt(IntRect destRect,
const Bitmap &source, IntRect sourceRect,
int opacity = 255);
int opacity = 255, bool smooth = false);
void fillRect(int x, int y,
int width, int height,