Merge pull request #21 from Splendide-Imaginarius/mkxp-z-hires

Implement high-resolution Bitmap replacement
This commit is contained in:
Splendide Imaginarius 2023-10-25 22:52:35 +00:00 committed by GitHub
commit d6d1d9699e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 1205 additions and 44 deletions

View file

@ -49,6 +49,12 @@ void bitmapInitProps(Bitmap *b, VALUE self) {
b->setInitFont(font);
rb_iv_set(self, "font", fontObj);
// Leave property as default nil if hasHires() is false.
if (b->hasHires()) {
b->assumeRubyGC();
wrapProperty(self, b->getHires(), "hires", BitmapType);
}
}
RB_METHOD(bitmapInitialize) {
@ -94,6 +100,8 @@ RB_METHOD(bitmapHeight) {
return INT2FIX(value);
}
DEF_GFX_PROP_OBJ_REF(Bitmap, Bitmap, Hires, "hires")
RB_METHOD(bitmapRect) {
RB_UNUSED_PARAM;
@ -741,6 +749,9 @@ void bitmapBindingInit() {
_rb_define_method(klass, "width", bitmapWidth);
_rb_define_method(klass, "height", bitmapHeight);
INIT_PROP_BIND(Bitmap, Hires, "hires");
_rb_define_method(klass, "rect", bitmapRect);
_rb_define_method(klass, "blt", bitmapBlt);
_rb_define_method(klass, "stretch_blt", bitmapStretchBlt);

View file

@ -19,6 +19,7 @@
** along with mkxp. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "graphics.h"
#include "sharedstate.h"
#include "binding-util.h"

View file

@ -91,6 +91,35 @@
// "lanczos3Scaling": false,
// Replace the game's Bitmap files with external high-res files
// provided in the "Hires" directory.
// (You'll also need to set the below Scaling Factors.)
// (default: disabled)
//
// "enableHires": false,
// Scaling factor for textures (e.g. Bitmaps)
// (higher values will look better if you use high-res textures)
// (default: 1.0)
//
// "textureScalingFactor": 4.0,
// Scaling factor for screen framebuffer
// (higher values will look better if you use high-res textures)
// (default: 1.0)
//
// "framebufferScalingFactor": 4.0,
// Scaling factor for tileset atlas
// (higher values will look better if you use high-res textures)
// (default: 1.0)
//
// "atlasScalingFactor": 4.0,
// Sync screen redraws to the monitor refresh rate
// (default: disabled)
//

View file

@ -136,6 +136,10 @@ void Config::read(int argc, char *argv[]) {
{"fixedAspectRatio", true},
{"smoothScaling", false},
{"lanczos3Scaling", false},
{"enableHires", false},
{"textureScalingFactor", 1.},
{"framebufferScalingFactor", 1.},
{"atlasScalingFactor", 1.},
{"vsync", false},
{"defScreenW", 0},
{"defScreenH", 0},
@ -261,6 +265,10 @@ try { exp } catch (...) {}
SET_OPT(fixedAspectRatio, boolean);
SET_OPT(smoothScaling, boolean);
SET_OPT(lanczos3Scaling, boolean);
SET_OPT(enableHires, boolean);
SET_OPT(textureScalingFactor, number);
SET_OPT(framebufferScalingFactor, number);
SET_OPT(atlasScalingFactor, number);
SET_OPT(winResizable, boolean);
SET_OPT(vsync, boolean);
SET_STRINGOPT(windowTitle, windowTitle);

View file

@ -45,6 +45,10 @@ struct Config {
bool fixedAspectRatio;
bool smoothScaling;
bool lanczos3Scaling;
bool enableHires;
double textureScalingFactor;
double framebufferScalingFactor;
double atlasScalingFactor;
bool vsync;
int defScreenW;

View file

@ -237,11 +237,19 @@ struct BitmapPrivate
* in the texture and blit to it directly, saving
* ourselves the expensive blending calculation */
pixman_region16_t tainted;
// For high-resolution texture replacement.
Bitmap *selfHires;
Bitmap *selfLores;
bool assumingRubyGC;
BitmapPrivate(Bitmap *self)
: self(self),
megaSurface(0),
surface(0)
selfHires(0),
selfLores(0),
surface(0),
assumingRubyGC(false)
{
format = SDL_AllocFormat(SDL_PIXELFORMAT_ABGR8888);
@ -326,16 +334,30 @@ struct BitmapPrivate
return result != PIXMAN_REGION_OUT;
}
void bindTexture(ShaderBase &shader)
void bindTexture(ShaderBase &shader, bool substituteLoresSize = true)
{
if (selfHires) {
selfHires->bindTex(shader);
return;
}
if (animation.enabled) {
if (selfLores) {
Debug() << "BUG: High-res BitmapPrivate bindTexture for animations not implemented";
}
TEXFBO cframe = animation.currentFrame();
TEX::bind(cframe.tex);
shader.setTexSize(Vec2i(cframe.width, cframe.height));
return;
}
TEX::bind(gl.tex);
shader.setTexSize(Vec2i(gl.width, gl.height));
if (selfLores && substituteLoresSize) {
shader.setTexSize(Vec2i(selfLores->width(), selfLores->height()));
}
else {
shader.setTexSize(Vec2i(gl.width, gl.height));
}
}
void bindFBO()
@ -468,6 +490,24 @@ struct BitmapOpenHandler : FileSystem::OpenHandler
Bitmap::Bitmap(const char *filename)
{
std::string hiresPrefix = "Hires/";
std::string filenameStd = filename;
Bitmap *hiresBitmap = nullptr;
// TODO: once C++20 is required, switch to filenameStd.starts_with(hiresPrefix)
if (shState->config().enableHires && filenameStd.compare(0, hiresPrefix.size(), hiresPrefix) != 0) {
// Look for a high-res version of the file.
std::string hiresFilename = hiresPrefix + filenameStd;
try {
hiresBitmap = new Bitmap(hiresFilename.c_str());
hiresBitmap->setLores(this);
}
catch (const Exception &e)
{
Debug() << "No high-res Bitmap found at" << hiresFilename;
hiresBitmap = nullptr;
}
}
BitmapOpenHandler handler;
shState->fileSystem().openRead(handler, filename);
@ -482,6 +522,8 @@ Bitmap::Bitmap(const char *filename)
if (handler.gif) {
p = new BitmapPrivate(this);
p->selfHires = hiresBitmap;
if (handler.gif->width >= (uint32_t)glState.caps.maxTexSize || handler.gif->height > (uint32_t)glState.caps.maxTexSize)
{
@ -510,6 +552,9 @@ Bitmap::Bitmap(const char *filename)
delete handler.gif_data;
p->gl = texfbo;
if (p->selfHires != nullptr) {
p->gl.selfHires = &p->selfHires->getGLTypes();
}
p->addTaintedArea(rect());
return;
}
@ -583,6 +628,7 @@ Bitmap::Bitmap(const char *filename)
{
/* Mega surface */
p = new BitmapPrivate(this);
p->selfHires = hiresBitmap;
p->megaSurface = imgSurf;
SDL_SetSurfaceBlendMode(p->megaSurface, SDL_BLENDMODE_NONE);
}
@ -602,7 +648,11 @@ Bitmap::Bitmap(const char *filename)
}
p = new BitmapPrivate(this);
p->selfHires = hiresBitmap;
p->gl = tex;
if (p->selfHires != nullptr) {
p->gl.selfHires = &p->selfHires->getGLTypes();
}
TEX::bind(p->gl.tex);
TEX::uploadImage(p->gl.width, p->gl.height, imgSurf->pixels, GL_RGBA);
@ -613,15 +663,30 @@ Bitmap::Bitmap(const char *filename)
p->addTaintedArea(rect());
}
Bitmap::Bitmap(int width, int height)
Bitmap::Bitmap(int width, int height, bool isHires)
{
if (width <= 0 || height <= 0)
throw Exception(Exception::RGSSError, "failed to create bitmap");
Bitmap *hiresBitmap = nullptr;
if (shState->config().enableHires && !isHires) {
// Create a high-res version as well.
double scalingFactor = shState->config().textureScalingFactor;
int hiresWidth = (int)lround(scalingFactor * width);
int hiresHeight = (int)lround(scalingFactor * height);
hiresBitmap = new Bitmap(hiresWidth, hiresHeight, true);
hiresBitmap->setLores(this);
}
TEXFBO tex = shState->texPool().request(width, height);
p = new BitmapPrivate(this);
p->gl = tex;
if (p->selfHires != nullptr) {
p->gl.selfHires = &p->selfHires->getGLTypes();
}
p->selfHires = hiresBitmap;
clear();
}
@ -679,6 +744,10 @@ Bitmap::Bitmap(const Bitmap &other, int frame)
other.ensureNonMega();
if (frame > -2) other.ensureAnimated();
if (other.hasHires()) {
Debug() << "BUG: High-res Bitmap from animation not implemented";
}
p = new BitmapPrivate(this);
// TODO: Clean me up
@ -762,6 +831,28 @@ int Bitmap::height() const
return p->gl.height;
}
bool Bitmap::hasHires() const{
guardDisposed();
return p->selfHires;
}
DEF_ATTR_RD_SIMPLE(Bitmap, Hires, Bitmap*, p->selfHires)
void Bitmap::setHires(Bitmap *hires) {
guardDisposed();
Debug() << "BUG: High-res Bitmap setHires not fully implemented, expect bugs";
hires->setLores(this);
p->selfHires = hires;
}
void Bitmap::setLores(Bitmap *lores) {
guardDisposed();
p->selfLores = lores;
}
bool Bitmap::isMega() const{
guardDisposed();
@ -809,13 +900,35 @@ void Bitmap::stretchBlt(const IntRect &destRect,
int opacity)
{
guardDisposed();
// Don't need this, right? This function is fine with megasurfaces it seems
//GUARD_MEGA;
if (source.isDisposed())
return;
if (hasHires()) {
int destX, destY, destWidth, destHeight;
destX = destRect.x * p->selfHires->width() / width();
destY = destRect.y * p->selfHires->height() / height();
destWidth = destRect.w * p->selfHires->width() / width();
destHeight = destRect.h * p->selfHires->height() / height();
p->selfHires->stretchBlt(IntRect(destX, destY, destWidth, destHeight), source, sourceRect, opacity);
return;
}
if (source.hasHires()) {
int sourceX, sourceY, sourceWidth, sourceHeight;
sourceX = sourceRect.x * source.getHires()->width() / source.width();
sourceY = sourceRect.y * source.getHires()->height() / source.height();
sourceWidth = sourceRect.w * source.getHires()->width() / source.width();
sourceHeight = sourceRect.h * source.getHires()->height() / source.height();
stretchBlt(destRect, *source.getHires(), IntRect(sourceX, sourceY, sourceWidth, sourceHeight), opacity);
return;
}
opacity = clamp(opacity, 0, 255);
if (opacity == 0)
@ -936,7 +1049,7 @@ void Bitmap::stretchBlt(const IntRect &destRect,
quad.setTexPosRect(sourceRect, destRect);
quad.setColor(Vec4(1, 1, 1, normOpacity));
source.p->bindTexture(shader);
source.p->bindTexture(shader, false);
p->bindFBO();
p->pushSetViewport(shader);
@ -963,6 +1076,16 @@ void Bitmap::fillRect(const IntRect &rect, const Vec4 &color)
GUARD_MEGA;
GUARD_ANIMATED;
if (hasHires()) {
int destX, destY, destWidth, destHeight;
destX = rect.x * p->selfHires->width() / width();
destY = rect.y * p->selfHires->height() / height();
destWidth = rect.w * p->selfHires->width() / width();
destHeight = rect.h * p->selfHires->height() / height();
p->selfHires->fillRect(IntRect(destX, destY, destWidth, destHeight), color);
}
p->fillRect(rect, color);
if (color.w == 0)
@ -992,6 +1115,16 @@ void Bitmap::gradientFillRect(const IntRect &rect,
GUARD_MEGA;
GUARD_ANIMATED;
if (hasHires()) {
int destX, destY, destWidth, destHeight;
destX = rect.x * p->selfHires->width() / width();
destY = rect.y * p->selfHires->height() / height();
destWidth = rect.w * p->selfHires->width() / width();
destHeight = rect.h * p->selfHires->height() / height();
p->selfHires->gradientFillRect(IntRect(destX, destY, destWidth, destHeight), color1, color2, vertical);
}
SimpleColorShader &shader = shState->shaders().simpleColor;
shader.bind();
shader.setTranslation(Vec2i());
@ -1039,6 +1172,16 @@ void Bitmap::clearRect(const IntRect &rect)
GUARD_MEGA;
GUARD_ANIMATED;
if (hasHires()) {
int destX, destY, destWidth, destHeight;
destX = rect.x * p->selfHires->width() / width();
destY = rect.y * p->selfHires->height() / height();
destWidth = rect.w * p->selfHires->width() / width();
destHeight = rect.h * p->selfHires->height() / height();
p->selfHires->clearRect(IntRect(destX, destY, destWidth, destHeight));
}
p->fillRect(rect, Vec4());
p->onModified();
@ -1051,6 +1194,12 @@ void Bitmap::blur()
GUARD_MEGA;
GUARD_ANIMATED;
if (hasHires()) {
p->selfHires->blur();
}
// TODO: Is there some kind of blur radius that we need to handle for high-res mode?
Quad &quad = shState->gpQuad();
FloatRect rect(0, 0, width(), height());
quad.setTexPosRect(rect, rect);
@ -1097,6 +1246,11 @@ void Bitmap::radialBlur(int angle, int divisions)
GUARD_MEGA;
GUARD_ANIMATED;
if (hasHires()) {
p->selfHires->radialBlur(angle, divisions);
return;
}
angle = clamp<int>(angle, 0, 359);
divisions = clamp<int>(divisions, 2, 100);
@ -1161,7 +1315,7 @@ void Bitmap::radialBlur(int angle, int divisions)
SimpleMatrixShader &shader = shState->shaders().simpleMatrix;
shader.bind();
p->bindTexture(shader);
p->bindTexture(shader, false);
TEX::setSmooth(true);
p->pushSetViewport(shader);
@ -1193,6 +1347,10 @@ void Bitmap::clear()
GUARD_MEGA;
GUARD_ANIMATED;
if (hasHires()) {
p->selfHires->clear();
}
p->bindFBO();
glState.clearColor.pushSet(Vec4());
@ -1221,9 +1379,52 @@ Color Bitmap::getPixel(int x, int y) const
GUARD_MEGA;
GUARD_ANIMATED;
if (hasHires()) {
Debug() << "GAME BUG: Game is calling getPixel on low-res Bitmap; you may want to patch the game to improve graphics quality.";
int xHires = x * p->selfHires->width() / width();
int yHires = y * p->selfHires->height() / height();
// We take the average color from the high-res Bitmap.
// RGB channels skip fully transparent pixels when averaging.
int w = p->selfHires->width() / width();
int h = p->selfHires->height() / height();
if (w >= 1 && h >= 1) {
double rSum = 0.;
double gSum = 0.;
double bSum = 0.;
double aSum = 0.;
long long rgbCount = 0;
long long aCount = 0;
for (int thisX = xHires; thisX < xHires+w && thisX < p->selfHires->width(); thisX++) {
for (int thisY = yHires; thisY < yHires+h && thisY < p->selfHires->height(); thisY++) {
Color thisColor = p->selfHires->getPixel(thisX, thisY);
if (thisColor.getAlpha() >= 1.0) {
rSum += thisColor.getRed();
gSum += thisColor.getGreen();
bSum += thisColor.getBlue();
rgbCount++;
}
aSum += thisColor.getAlpha();
aCount++;
}
}
double rAvg = rSum / (double)rgbCount;
double gAvg = gSum / (double)rgbCount;
double bAvg = bSum / (double)rgbCount;
double aAvg = aSum / (double)aCount;
return Color(rAvg, gAvg, bAvg, aAvg);
}
}
if (x < 0 || y < 0 || x >= width() || y >= height())
return Vec4();
if (!p->surface)
{
p->allocSurface();
@ -1252,6 +1453,24 @@ void Bitmap::setPixel(int x, int y, const Color &color)
GUARD_MEGA;
GUARD_ANIMATED;
if (hasHires()) {
Debug() << "GAME BUG: Game is calling setPixel on low-res Bitmap; you may want to patch the game to improve graphics quality.";
int xHires = x * p->selfHires->width() / width();
int yHires = y * p->selfHires->height() / height();
int w = p->selfHires->width() / width();
int h = p->selfHires->height() / height();
if (w >= 1 && h >= 1) {
for (int thisX = xHires; thisX < xHires+w && thisX < p->selfHires->width(); thisX++) {
for (int thisY = yHires; thisY < yHires+h && thisY < p->selfHires->height(); thisY++) {
p->selfHires->setPixel(thisX, thisY, color);
}
}
}
}
uint8_t pixel[] =
{
(uint8_t) clamp<double>(color.red, 0, 255),
@ -1283,6 +1502,10 @@ bool Bitmap::getRaw(void *output, int output_size)
guardDisposed();
if (hasHires()) {
Debug() << "GAME BUG: Game is calling getRaw on low-res Bitmap; you may want to patch the game to improve graphics quality.";
}
if (!p->animation.enabled && (p->surface || p->megaSurface)) {
void *src = (p->megaSurface) ? p->megaSurface->pixels : p->surface->pixels;
memcpy(output, src, output_size);
@ -1300,6 +1523,10 @@ void Bitmap::replaceRaw(void *pixel_data, int size)
GUARD_MEGA;
if (hasHires()) {
Debug() << "GAME BUG: Game is calling replaceRaw on low-res Bitmap; you may want to patch the game to improve graphics quality.";
}
int w = width();
int h = height();
int requiredsize = w*h*4;
@ -1318,6 +1545,10 @@ void Bitmap::saveToFile(const char *filename)
{
guardDisposed();
if (hasHires()) {
Debug() << "GAME BUG: Game is calling saveToFile on low-res Bitmap; you may want to patch the game to improve graphics quality.";
}
SDL_Surface *surf;
if (p->surface || p->megaSurface) {
@ -1377,6 +1608,11 @@ void Bitmap::hueChange(int hue)
GUARD_MEGA;
GUARD_ANIMATED;
if (hasHires()) {
p->selfHires->hueChange(hue);
return;
}
if ((hue % 360) == 0)
return;
@ -1395,7 +1631,7 @@ void Bitmap::hueChange(int hue)
FBO::bind(newTex.fbo);
p->pushSetViewport(shader);
p->bindTexture(shader);
p->bindTexture(shader, false);
p->blitQuad(quad);
@ -1525,6 +1761,26 @@ void Bitmap::drawText(const IntRect &rect, const char *str, int align)
GUARD_MEGA;
GUARD_ANIMATED;
if (hasHires()) {
Font &loresFont = getFont();
Font &hiresFont = p->selfHires->getFont();
// Disable the illegal font size check when creating a high-res font.
hiresFont.setSize(loresFont.getSize() * p->selfHires->width() / width(), false);
hiresFont.setBold(loresFont.getBold());
hiresFont.setColor(loresFont.getColor());
hiresFont.setItalic(loresFont.getItalic());
hiresFont.setShadow(loresFont.getShadow());
hiresFont.setOutline(loresFont.getOutline());
hiresFont.setOutColor(loresFont.getOutColor());
int rectX = rect.x * p->selfHires->width() / width();
int rectY = rect.y * p->selfHires->height() / height();
int rectWidth = rect.w * p->selfHires->width() / width();
int rectHeight = rect.h * p->selfHires->height() / height();
p->selfHires->drawText(IntRect(rectX, rectY, rectWidth, rectHeight), str, align);
}
std::string fixed = fixupString(str);
str = fixed.c_str();
@ -1564,15 +1820,20 @@ void Bitmap::drawText(const IntRect &rect, const char *str, int align)
SDL_Color co = outColor.toSDLColor();
co.a = 255;
SDL_Surface *outline;
// Handle high-res for outline.
int scaledOutlineSize = OUTLINE_SIZE;
if (p->selfLores) {
scaledOutlineSize = scaledOutlineSize * width() / p->selfLores->width();
}
/* set the next font render to render the outline */
TTF_SetFontOutline(font, OUTLINE_SIZE);
TTF_SetFontOutline(font, scaledOutlineSize);
if (p->font->isSolid())
outline = TTF_RenderUTF8_Solid(font, str, co);
else
outline = TTF_RenderUTF8_Blended(font, str, co);
p->ensureFormat(outline, SDL_PIXELFORMAT_ABGR8888);
SDL_Rect outRect = {OUTLINE_SIZE, OUTLINE_SIZE, txtSurf->w, txtSurf->h};
SDL_Rect outRect = {scaledOutlineSize, scaledOutlineSize, txtSurf->w, txtSurf->h};
SDL_SetSurfaceBlendMode(txtSurf, SDL_BLENDMODE_BLEND);
SDL_BlitSurface(txtSurf, NULL, outline, &outRect);
@ -1787,6 +2048,9 @@ IntRect Bitmap::textSize(const char *str)
GUARD_MEGA;
GUARD_ANIMATED;
// TODO: High-res Bitmap textSize not implemented, but I think it's the same as low-res?
// Need to double-check this.
TTF_Font *font = p->font->getSdlFont();
std::string fixed = fixupString(str);
@ -1811,11 +2075,19 @@ DEF_ATTR_RD_SIMPLE(Bitmap, Font, Font&, *p->font)
void Bitmap::setFont(Font &value)
{
// High-res support handled in drawText, not here.
*p->font = value;
}
void Bitmap::setInitFont(Font *value)
{
if (hasHires()) {
Font *hiresFont = new Font(*value);
// Disable the illegal font size check when creating a high-res font.
hiresFont->setSize(hiresFont->getSize() * p->selfHires->width() / width(), false);
p->selfHires->setInitFont(hiresFont);
}
p->font = value;
}
@ -1826,11 +2098,24 @@ TEXFBO &Bitmap::getGLTypes() const
SDL_Surface *Bitmap::surface() const
{
if (hasHires()) {
Debug() << "BUG: High-res Bitmap surface not implemented";
}
return p->surface;
}
SDL_Surface *Bitmap::megaSurface() const
{
if (hasHires()) {
if (p->megaSurface) {
Debug() << "BUG: High-res Bitmap megaSurface not implemented (low-res has megaSurface)";
}
if (p->selfHires->megaSurface()) {
Debug() << "BUG: High-res Bitmap megaSurface not implemented (high-res has megaSurface)";
}
}
return p->megaSurface;
}
@ -1865,6 +2150,10 @@ void Bitmap::stop()
GUARD_UNANIMATED;
if (!p->animation.playing) return;
if (hasHires()) {
Debug() << "BUG: High-res Bitmap stop not implemented";
}
p->animation.stop();
}
@ -1874,6 +2163,11 @@ void Bitmap::play()
GUARD_UNANIMATED;
if (p->animation.playing) return;
if (hasHires()) {
Debug() << "BUG: High-res Bitmap play not implemented";
}
p->animation.play();
}
@ -1881,6 +2175,10 @@ bool Bitmap::isPlaying() const
{
guardDisposed();
if (hasHires()) {
Debug() << "BUG: High-res Bitmap isPlaying not implemented";
}
if (!p->animation.playing)
return false;
@ -1896,6 +2194,10 @@ void Bitmap::gotoAndStop(int frame)
GUARD_UNANIMATED;
if (hasHires()) {
Debug() << "BUG: High-res Bitmap gotoAndStop not implemented";
}
p->animation.stop();
p->animation.seek(frame);
}
@ -1905,6 +2207,10 @@ void Bitmap::gotoAndPlay(int frame)
GUARD_UNANIMATED;
if (hasHires()) {
Debug() << "BUG: High-res Bitmap gotoAndPlay not implemented";
}
p->animation.stop();
p->animation.seek(frame);
p->animation.play();
@ -1914,6 +2220,10 @@ int Bitmap::numFrames() const
{
guardDisposed();
if (hasHires()) {
Debug() << "BUG: High-res Bitmap numFrames not implemented";
}
if (!p->animation.enabled) return 1;
return (int)p->animation.frames.size();
}
@ -1922,6 +2232,10 @@ int Bitmap::currentFrameI() const
{
guardDisposed();
if (hasHires()) {
Debug() << "BUG: High-res Bitmap currentFrameI not implemented";
}
if (!p->animation.enabled) return 0;
return p->animation.currentFrameI();
}
@ -1932,6 +2246,14 @@ int Bitmap::addFrame(Bitmap &source, int position)
GUARD_MEGA;
if (hasHires()) {
Debug() << "BUG: High-res Bitmap addFrame dest not implemented";
}
if (source.hasHires()) {
Debug() << "BUG: High-res Bitmap addFrame source not implemented";
}
if (source.height() != height() || source.width() != width())
throw Exception(Exception::MKXPError, "Animations with varying dimensions are not supported (%ix%i vs %ix%i)",
source.width(), source.height(), width(), height());
@ -1989,6 +2311,10 @@ void Bitmap::removeFrame(int position) {
GUARD_UNANIMATED;
if (hasHires()) {
Debug() << "BUG: High-res Bitmap removeFrame not implemented";
}
int pos = (position < 0) ? (int)p->animation.frames.size() - 1 : clamp(position, 0, (int)(p->animation.frames.size() - 1));
shState->texPool().release(p->animation.frames[pos]);
p->animation.frames.erase(p->animation.frames.begin() + pos);
@ -2016,6 +2342,10 @@ void Bitmap::nextFrame()
GUARD_UNANIMATED;
if (hasHires()) {
Debug() << "BUG: High-res Bitmap nextFrame not implemented";
}
stop();
if ((uint32_t)p->animation.lastFrame >= p->animation.frames.size() - 1) {
if (!p->animation.loop) return;
@ -2032,6 +2362,10 @@ void Bitmap::previousFrame()
GUARD_UNANIMATED;
if (hasHires()) {
Debug() << "BUG: High-res Bitmap previousFrame not implemented";
}
stop();
if (p->animation.lastFrame <= 0) {
if (!p->animation.loop) {
@ -2051,6 +2385,10 @@ void Bitmap::setAnimationFPS(float FPS)
GUARD_MEGA;
if (hasHires()) {
Debug() << "BUG: High-res Bitmap setAnimationFPS not implemented";
}
bool restart = p->animation.playing;
p->animation.stop();
p->animation.fps = (FPS < 0) ? 0 : FPS;
@ -2059,6 +2397,10 @@ void Bitmap::setAnimationFPS(float FPS)
std::vector<TEXFBO> &Bitmap::getFrames() const
{
if (hasHires()) {
Debug() << "BUG: High-res Bitmap getFrames not implemented";
}
return p->animation.frames;
}
@ -2068,6 +2410,10 @@ float Bitmap::getAnimationFPS() const
GUARD_MEGA;
if (hasHires()) {
Debug() << "BUG: High-res Bitmap getAnimationFPS not implemented";
}
return p->animation.fps;
}
@ -2077,6 +2423,10 @@ void Bitmap::setLooping(bool loop)
GUARD_MEGA;
if (hasHires()) {
Debug() << "BUG: High-res Bitmap setLooping not implemented";
}
p->animation.loop = loop;
}
@ -2086,16 +2436,32 @@ bool Bitmap::getLooping() const
GUARD_MEGA;
if (hasHires()) {
Debug() << "BUG: High-res Bitmap getLooping not implemented";
}
return p->animation.loop;
}
void Bitmap::bindTex(ShaderBase &shader)
{
// Hires mode is handled by p->bindTexture.
p->bindTexture(shader);
}
void Bitmap::taintArea(const IntRect &rect)
{
if (hasHires()) {
int destX, destY, destWidth, destHeight;
destX = rect.x * p->selfHires->width() / width();
destY = rect.y * p->selfHires->height() / height();
destWidth = rect.w * p->selfHires->width() / width();
destHeight = rect.h * p->selfHires->height() / height();
p->selfHires->taintArea(IntRect(destX, destY, destWidth, destHeight));
}
p->addTaintedArea(rect);
}
@ -2121,8 +2487,17 @@ bool Bitmap::invalid() const {
return p == 0;
}
void Bitmap::assumeRubyGC()
{
p->assumingRubyGC = true;
}
void Bitmap::releaseResources()
{
if (p->selfHires && !p->assumingRubyGC) {
delete p->selfHires;
}
if (p->megaSurface)
SDL_FreeSurface(p->megaSurface);
else if (p->animation.enabled) {

View file

@ -39,7 +39,7 @@ class Bitmap : public Disposable
{
public:
Bitmap(const char *filename);
Bitmap(int width, int height);
Bitmap(int width, int height, bool isHires = false);
Bitmap(void *pixeldata, int width, int height);
/* Clone constructor */
@ -49,6 +49,9 @@ public:
int width() const;
int height() const;
bool hasHires() const;
DECL_ATTR(Hires, Bitmap*)
void setLores(Bitmap *lores);
bool isMega() const;
bool isAnimated() const;
@ -161,6 +164,8 @@ public:
bool invalid() const;
void assumeRubyGC();
private:
void releaseResources();
const char *klassName() const { return "bitmap"; }

View file

@ -399,14 +399,17 @@ void Font::setName(const std::vector<std::string> &names)
p->sdlFont = 0;
}
void Font::setSize(int value)
void Font::setSize(int value, bool checkIllegal)
{
if (p->size == value)
return;
/* Catch illegal values (according to RMXP) */
if (value < 6 || value > 96)
throw Exception(Exception::ArgumentError, "%s", "bad value for size");
if (value < 6 || value > 96) {
if (checkIllegal) {
throw Exception(Exception::ArgumentError, "%s", "bad value for size");
}
}
p->size = value;
p->sdlFont = 0;

View file

@ -76,7 +76,9 @@ public:
const Font &operator=(const Font &o);
DECL_ATTR( Size, int )
int getSize() const;
void setSize(int value, bool checkIllegal=true);
DECL_ATTR( Bold, bool )
DECL_ATTR( Italic, bool )
DECL_ATTR( Color, Color& )

View file

@ -26,6 +26,11 @@
#include "quad.h"
#include "config.h"
namespace FBO
{
ID boundFramebufferID;
}
namespace GLMeta
{
@ -138,6 +143,7 @@ static void _blitBegin(FBO::ID fbo, const Vec2i &size)
{
if (HAVE_NATIVE_BLIT)
{
FBO::boundFramebufferID = fbo;
gl.BindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo.gl);
}
else
@ -164,18 +170,56 @@ static void _blitBegin(FBO::ID fbo, const Vec2i &size)
}
}
void blitBegin(TEXFBO &target)
int blitDstWidthLores = 1;
int blitDstWidthHires = 1;
int blitDstHeightLores = 1;
int blitDstHeightHires = 1;
int blitSrcWidthLores = 1;
int blitSrcWidthHires = 1;
int blitSrcHeightLores = 1;
int blitSrcHeightHires = 1;
void blitBegin(TEXFBO &target, bool preferHires)
{
_blitBegin(target.fbo, Vec2i(target.width, target.height));
blitDstWidthLores = target.width;
blitDstHeightLores = target.height;
if (preferHires && target.selfHires != nullptr) {
blitDstWidthHires = target.selfHires->width;
blitDstHeightHires = target.selfHires->height;
_blitBegin(target.selfHires->fbo, Vec2i(target.selfHires->width, target.selfHires->height));
}
else {
blitDstWidthHires = blitDstWidthLores;
blitDstHeightHires = blitDstHeightLores;
_blitBegin(target.fbo, Vec2i(target.width, target.height));
}
}
void blitBeginScreen(const Vec2i &size)
{
blitDstWidthLores = 1;
blitDstWidthHires = 1;
blitDstHeightLores = 1;
blitDstHeightHires = 1;
_blitBegin(FBO::ID(0), size);
}
void blitSource(TEXFBO &source)
{
blitSrcWidthLores = source.width;
blitSrcHeightLores = source.height;
if (source.selfHires != nullptr) {
blitSrcWidthHires = source.selfHires->width;
blitSrcHeightHires = source.selfHires->height;
}
else {
blitSrcWidthHires = blitSrcWidthLores;
blitSrcHeightHires = blitSrcHeightLores;
}
if (HAVE_NATIVE_BLIT)
{
gl.BindFramebuffer(GL_READ_FRAMEBUFFER, source.fbo.gl);
@ -186,15 +230,20 @@ void blitSource(TEXFBO &source)
{
Lanczos3Shader &shader = shState->shaders().lanczos3;
shader.bind();
shader.setTexSize(Vec2i(source.width, source.height));
shader.setTexSize(Vec2i(blitSrcWidthHires, blitSrcHeightHires));
}
else
{
SimpleShader &shader = shState->shaders().simple;
shader.bind();
shader.setTexSize(Vec2i(source.width, source.height));
shader.setTexSize(Vec2i(blitSrcWidthHires, blitSrcHeightHires));
}
if (source.selfHires != nullptr) {
TEX::bind(source.selfHires->tex);
}
else {
TEX::bind(source.tex);
}
TEX::bind(source.tex);
}
}
@ -205,10 +254,24 @@ void blitRectangle(const IntRect &src, const Vec2i &dstPos)
void blitRectangle(const IntRect &src, const IntRect &dst, bool smooth)
{
// Handle high-res dest
int scaledDstX = dst.x * blitDstWidthHires / blitDstWidthLores;
int scaledDstY = dst.y * blitDstHeightHires / blitDstHeightLores;
int scaledDstWidth = dst.w * blitDstWidthHires / blitDstWidthLores;
int scaledDstHeight = dst.h * blitDstHeightHires / blitDstHeightLores;
IntRect dstScaled(scaledDstX, scaledDstY, scaledDstWidth, scaledDstHeight);
// Handle high-res source
int scaledSrcX = src.x * blitSrcWidthHires / blitSrcWidthLores;
int scaledSrcY = src.y * blitSrcHeightHires / blitSrcHeightLores;
int scaledSrcWidth = src.w * blitSrcWidthHires / blitSrcWidthLores;
int scaledSrcHeight = src.h * blitSrcHeightHires / blitSrcHeightLores;
IntRect srcScaled(scaledSrcX, scaledSrcY, scaledSrcWidth, scaledSrcHeight);
if (HAVE_NATIVE_BLIT)
{
gl.BlitFramebuffer(src.x, src.y, src.x+src.w, src.y+src.h,
dst.x, dst.y, dst.x+dst.w, dst.y+dst.h,
gl.BlitFramebuffer(srcScaled.x, srcScaled.y, srcScaled.x+srcScaled.w, srcScaled.y+srcScaled.h,
dstScaled.x, dstScaled.y, dstScaled.x+dstScaled.w, dstScaled.y+dstScaled.h,
GL_COLOR_BUFFER_BIT, smooth ? GL_LINEAR : GL_NEAREST);
}
else
@ -218,7 +281,7 @@ void blitRectangle(const IntRect &src, const IntRect &dst, bool smooth)
glState.blend.pushSet(false);
Quad &quad = shState->gpQuad();
quad.setTexPosRect(src, dst);
quad.setTexPosRect(srcScaled, dstScaled);
quad.draw();
glState.blend.pop();
@ -229,8 +292,19 @@ void blitRectangle(const IntRect &src, const IntRect &dst, bool smooth)
void blitEnd()
{
if (!HAVE_NATIVE_BLIT)
blitDstWidthLores = 1;
blitDstWidthHires = 1;
blitDstHeightLores = 1;
blitDstHeightHires = 1;
blitSrcWidthLores = 1;
blitSrcWidthHires = 1;
blitSrcHeightLores = 1;
blitSrcHeightHires = 1;
if (!HAVE_NATIVE_BLIT) {
glState.viewport.pop();
}
}
}

View file

@ -65,7 +65,7 @@ void vaoBind(VAO &vao);
void vaoUnbind(VAO &vao);
/* EXT_framebuffer_blit */
void blitBegin(TEXFBO &target);
void blitBegin(TEXFBO &target, bool preferHires = false);
void blitBeginScreen(const Vec2i &size);
void blitSource(TEXFBO &source);
void blitRectangle(const IntRect &src, const Vec2i &dstPos);

View file

@ -109,6 +109,8 @@ namespace FBO
{
DEF_GL_ID
extern ID boundFramebufferID;
inline ID gen()
{
ID id;
@ -124,6 +126,7 @@ namespace FBO
static inline void bind(ID id)
{
boundFramebufferID = id;
gl.BindFramebuffer(GL_FRAMEBUFFER, id.gl);
}
@ -203,8 +206,10 @@ struct TEXFBO
FBO::ID fbo;
int width, height;
TEXFBO *selfHires;
TEXFBO()
: tex(0), fbo(0), width(0), height(0)
: tex(0), fbo(0), width(0), height(0), selfHires(nullptr)
{}
bool operator==(const TEXFBO &other) const

View file

@ -23,7 +23,9 @@
#include "config.h"
#include "etc.h"
#include "gl-fun.h"
#include "graphics.h"
#include "shader.h"
#include "sharedstate.h"
#include <SDL_rect.h>
@ -36,7 +38,19 @@ void GLClearColor::apply(const Vec4 &value) {
}
void GLScissorBox::apply(const IntRect &value) {
gl.Scissor(value.x, value.y, value.w, value.h);
// High-res: scale the scissorbox if we're rendering to the PingPong framebuffer.
if (shState) {
const double framebufferScalingFactor = shState->config().framebufferScalingFactor;
if (shState->config().enableHires && shState->graphics().isPingPongFramebufferActive()) {
gl.Scissor((int)lround(framebufferScalingFactor * value.x), (int)lround(framebufferScalingFactor * value.y), (int)lround(framebufferScalingFactor * value.w), (int)lround(framebufferScalingFactor * value.h));
}
else {
gl.Scissor(value.x, value.y, value.w, value.h);
}
}
else {
gl.Scissor(value.x, value.y, value.w, value.h);
}
}
void GLScissorBox::setIntersect(const IntRect &value) {

View file

@ -22,6 +22,8 @@
#ifndef QUAD_H
#define QUAD_H
#include "config.h"
#include "graphics.h"
#include "vertex.h"
#include "gl-util.h"
#include "gl-meta.h"

View file

@ -20,6 +20,8 @@
*/
#include "shader.h"
#include "config.h"
#include "graphics.h"
#include "sharedstate.h"
#include "glstate.h"
#include "exception.h"
@ -293,8 +295,19 @@ void ShaderBase::init()
void ShaderBase::applyViewportProj()
{
// High-res: scale the matrix if we're rendering to the PingPong framebuffer.
const IntRect &vp = glState.viewport.get();
projMat.set(Vec2i(vp.w, vp.h));
if (shState->config().enableHires && shState->graphics().isPingPongFramebufferActive() && framebufferScalingAllowed()) {
projMat.set(Vec2i(shState->graphics().width(), shState->graphics().height()));
}
else {
projMat.set(Vec2i(vp.w, vp.h));
}
}
bool ShaderBase::framebufferScalingAllowed()
{
return true;
}
void ShaderBase::setTexSize(const Vec2i &value)
@ -593,6 +606,13 @@ GrayShader::GrayShader()
GET_U(gray);
}
bool GrayShader::framebufferScalingAllowed()
{
// This shader is used with input textures that have already had a
// framebuffer scale applied. So we don't want to double-apply it.
return false;
}
void GrayShader::setGray(float value)
{
gl.Uniform1f(u_gray, value);

View file

@ -91,6 +91,7 @@ public:
protected:
void init();
virtual bool framebufferScalingAllowed();
GLint u_texSizeInv, u_translation;
};
@ -226,6 +227,9 @@ public:
void setGray(float value);
protected:
virtual bool framebufferScalingAllowed();
private:
GLint u_gray;
};

View file

@ -24,6 +24,7 @@
#include "tilemap-common.h"
#include "bitmap.h"
#include "table.h"
#include "debugwriter.h"
#include "etc-internal.h"
#include "gl-util.h"
#include "gl-meta.h"
@ -273,7 +274,7 @@ void build(TEXFBO &tf, Bitmap *bitmaps[BM_COUNT])
{
assert(tf.width == ATLASVX_W && tf.height == ATLASVX_H);
GLMeta::blitBegin(tf);
GLMeta::blitBegin(tf, true);
glState.clearColor.pushSet(Vec4());
FBO::clear();
@ -282,13 +283,36 @@ void build(TEXFBO &tf, Bitmap *bitmaps[BM_COUNT])
if (rgssVer >= 3)
{
SDL_Surface *shadow = createShadowSet();
TEX::bind(tf.tex);
TEX::uploadSubImage(shadowArea.x*32, shadowArea.y*32,
shadow->w, shadow->h, shadow->pixels, GL_RGBA);
if (tf.selfHires != nullptr) {
SDL_Rect srcRect({0, 0, shadow->w, shadow->h});
int destX = shadowArea.x*32 * tf.selfHires->width / tf.width;
int destY = shadowArea.y*32 * tf.selfHires->height / tf.height;
int destWidth = shadow->w * tf.selfHires->width / tf.width;
int destHeight = shadow->h * tf.selfHires->height / tf.height;
int bpp;
Uint32 rMask, gMask, bMask, aMask;
SDL_PixelFormatEnumToMasks(SDL_PIXELFORMAT_ABGR8888,
&bpp, &rMask, &gMask, &bMask, &aMask);
SDL_Surface *blitTemp =
SDL_CreateRGBSurface(0, destWidth, destHeight, bpp, rMask, gMask, bMask, aMask);
SDL_BlitScaled(shadow, &srcRect, blitTemp, 0);
TEX::bind(tf.selfHires->tex);
TEX::uploadSubImage(destX, destY,
blitTemp->w, blitTemp->h, blitTemp->pixels, GL_RGBA);
}
else {
TEX::bind(tf.tex);
TEX::uploadSubImage(shadowArea.x*32, shadowArea.y*32,
shadow->w, shadow->h, shadow->pixels, GL_RGBA);
}
SDL_FreeSurface(shadow);
}
Bitmap *bm;
#define EXEC_BLITS(part) \
if (!nullOrDisposed(bm = bitmaps[BM_##part])) \
{ \

View file

@ -772,6 +772,7 @@ struct GraphicsPrivate {
* RGSS renders at (settable with Graphics.resize_screen).
* Can only be changed from within RGSS */
Vec2i scRes;
Vec2i scResLores;
/* Screen size, to which the rendered frames are scaled up.
* This can be smaller than the window size when fixed aspect
@ -828,7 +829,7 @@ struct GraphicsPrivate {
IntruList<Disposable> dispList;
GraphicsPrivate(RGSSThreadData *rtData)
: scRes(DEF_SCREEN_W, DEF_SCREEN_H), scSize(scRes),
: scRes(DEF_SCREEN_W, DEF_SCREEN_H), scResLores(scRes), scSize(scRes),
winSize(rtData->config.defScreenW, rtData->config.defScreenH),
screen(scRes.x, scRes.y), threadData(rtData),
glCtx(SDL_GL_GetCurrentContext()), multithreadedMode(true),
@ -999,11 +1000,15 @@ struct GraphicsPrivate {
}
void compositeToBuffer(TEXFBO &buffer) {
compositeToBufferScaled(buffer, scRes.x, scRes.y);
}
void compositeToBufferScaled(TEXFBO &buffer, int destWidth, int destHeight) {
screen.composite();
GLMeta::blitBegin(buffer);
GLMeta::blitSource(screen.getPP().frontBuffer());
GLMeta::blitRectangle(IntRect(0, 0, scRes.x, scRes.y), Vec2i());
GLMeta::blitRectangle(IntRect(0, 0, scRes.x, scRes.y), IntRect(0, 0, destWidth, destHeight));
GLMeta::blitEnd();
}
@ -1229,22 +1234,28 @@ void Graphics::transition(int duration, const char *filename, int vague) {
TransShader &transShader = shState->shaders().trans;
SimpleTransShader &simpleShader = shState->shaders().simpleTrans;
// Handle high-res.
Vec2i transSize(p->scResLores.x, p->scResLores.y);
if (transMap) {
TransShader &shader = transShader;
shader.bind();
shader.applyViewportProj();
shader.setFrozenScene(p->frozenScene.tex);
shader.setCurrentScene(currentScene.tex);
if (transMap->hasHires()) {
Debug() << "BUG: High-res Graphics transMap not implemented";
}
shader.setTransMap(transMap->getGLTypes().tex);
shader.setVague(vague / 256.0f);
shader.setTexSize(p->scRes);
shader.setTexSize(transSize);
} else {
SimpleTransShader &shader = simpleShader;
shader.bind();
shader.applyViewportProj();
shader.setFrozenScene(p->frozenScene.tex);
shader.setCurrentScene(currentScene.tex);
shader.setTexSize(p->scRes);
shader.setTexSize(transSize);
}
glState.blend.pushSet(false);
@ -1391,16 +1402,36 @@ void Graphics::fadein(int duration) {
Bitmap *Graphics::snapToBitmap() {
Bitmap *bitmap = new Bitmap(width(), height());
p->compositeToBuffer(bitmap->getGLTypes());
if (bitmap->hasHires()) {
p->compositeToBufferScaled(bitmap->getHires()->getGLTypes(), bitmap->getHires()->width(), bitmap->getHires()->height());
}
p->compositeToBufferScaled(bitmap->getGLTypes(), bitmap->width(), bitmap->height());
/* Taint entire bitmap */
bitmap->taintArea(IntRect(0, 0, width(), height()));
return bitmap;
}
int Graphics::width() const { return p->scRes.x; }
int Graphics::width() const { return p->scResLores.x; }
int Graphics::height() const { return p->scRes.y; }
int Graphics::height() const { return p->scResLores.y; }
int Graphics::widthHires() const { return p->scRes.x; }
int Graphics::heightHires() const { return p->scRes.y; }
bool Graphics::isPingPongFramebufferActive() const {
return p->screen.getPP().frontBuffer().fbo == FBO::boundFramebufferID || p->screen.getPP().backBuffer().fbo == FBO::boundFramebufferID;
}
int Graphics::displayContentWidth() const {
return p->scSize.x;
}
int Graphics::displayContentHeight() const {
return p->scSize.y;
}
int Graphics::displayWidth() const {
SDL_DisplayMode dm{};
@ -1418,12 +1449,21 @@ void Graphics::resizeScreen(int width, int height) {
p->threadData->rqWindowAdjust.wait();
p->checkResize(true);
Vec2i sizeLores(width, height);
if (shState->config().enableHires) {
double framebufferScalingFactor = shState->config().framebufferScalingFactor;
width = (int)lround(framebufferScalingFactor * width);
height = (int)lround(framebufferScalingFactor * height);
}
Vec2i size(width, height);
if (p->scRes == size)
return;
p->scRes = size;
p->scResLores = sizeLores;
p->screen.setResolution(width, height);
@ -1459,6 +1499,10 @@ bool Graphics::updateMovieInput(Movie *movie) {
}
void Graphics::playMovie(const char *filename, int volume_, bool skippable) {
if (shState->config().enableHires) {
Debug() << "BUG: High-res Graphics playMovie not implemented";
}
Movie *movie = new Movie(skippable);
MovieOpenHandler handler(movie->srcOps);
shState->fileSystem().openRead(handler, filename);

View file

@ -58,6 +58,11 @@ public:
int width() const;
int height() const;
int widthHires() const;
int heightHires() const;
bool isPingPongFramebufferActive() const;
int displayContentWidth() const;
int displayContentHeight() const;
int displayWidth() const;
int displayHeight() const;
void resizeScreen(int width, int height);

View file

@ -23,6 +23,7 @@
#include "sharedstate.h"
#include "bitmap.h"
#include "debugwriter.h"
#include "etc.h"
#include "etc-internal.h"
#include "util.h"
@ -605,6 +606,10 @@ void Sprite::draw()
shader.setBushOpacity(p->bushOpacity.norm);
if (p->pattern && p->patternOpacity > 0) {
if (p->pattern->hasHires()) {
Debug() << "BUG: High-res Sprite pattern not implemented";
}
shader.setPattern(p->pattern->getGLTypes().tex, Vec2(p->pattern->width(), p->pattern->height()));
shader.setPatternBlendType(p->patternBlendType);
shader.setPatternTile(p->patternTile);

View file

@ -27,6 +27,7 @@
#include "sharedstate.h"
#include "config.h"
#include "debugwriter.h"
#include "glstate.h"
#include "gl-util.h"
#include "gl-meta.h"
@ -550,6 +551,10 @@ struct TilemapPrivate
int blitW = std::min(atW, atAreaW);
int blitH = std::min(atH, autotileH);
if (autotile->hasHires()) {
Debug() << "BUG: High-res Tilemap blit autotiles not implemented";
}
GLMeta::blitSource(autotile->getGLTypes());
if (atW <= autotileW && tiles.animated && !atlas.smallATs[atInd])
@ -639,6 +644,10 @@ struct TilemapPrivate
}
else
{
if (tileset->hasHires()) {
Debug() << "BUG: High-res Tilemap regular tileset not implemented";
}
/* Regular tileset */
GLMeta::blitBegin(atlas.gl);
GLMeta::blitSource(tileset->getGLTypes());

View file

@ -72,6 +72,8 @@ struct TilemapVXPrivate : public ViewportElement, TileAtlasVX::Reader
VBO::ID vbo;
GLMeta::VAO vao;
TEXFBO atlasHires;
size_t allocQuads;
size_t groundQuads;
@ -132,6 +134,14 @@ struct TilemapVXPrivate : public ViewportElement, TileAtlasVX::Reader
shState->requestAtlasTex(ATLASVX_W, ATLASVX_H, atlas);
if (shState->config().enableHires) {
double scalingFactor = shState->config().atlasScalingFactor;
int hiresWidth = (int)lround(scalingFactor * ATLASVX_W);
int hiresHeight = (int)lround(scalingFactor * ATLASVX_H);
shState->requestAtlasTex(hiresWidth, hiresHeight, atlasHires);
atlas.selfHires = &atlasHires;
}
vbo = VBO::gen();
GLMeta::vaoFillInVertexData<SVertex>(vao);
@ -151,6 +161,9 @@ struct TilemapVXPrivate : public ViewportElement, TileAtlasVX::Reader
VBO::del(vbo);
shState->releaseAtlasTex(atlas);
if (shState->config().enableHires) {
shState->releaseAtlasTex(atlasHires);
}
prepareCon.disconnect();
@ -310,7 +323,12 @@ struct TilemapVXPrivate : public ViewportElement, TileAtlasVX::Reader
shader->applyViewportProj();
shader->setTranslation(dispPos);
TEX::bind(atlas.tex);
if (atlas.selfHires != nullptr) {
TEX::bind(atlas.selfHires->tex);
}
else {
TEX::bind(atlas.tex);
}
GLMeta::vaoBind(vao);
gl.DrawElements(GL_TRIANGLES, groundQuads*6, _GL_INDEX_TYPE, 0);
@ -329,7 +347,12 @@ struct TilemapVXPrivate : public ViewportElement, TileAtlasVX::Reader
shader.applyViewportProj();
shader.setTranslation(dispPos);
TEX::bind(atlas.tex);
if (atlas.selfHires != nullptr) {
TEX::bind(atlas.selfHires->tex);
}
else {
TEX::bind(atlas.tex);
}
GLMeta::vaoBind(vao);
gl.DrawElements(GL_TRIANGLES, aboveQuads*6, _GL_INDEX_TYPE,

View file

@ -0,0 +1,412 @@
# Test suite for mkxp-z high-res Bitmap replacement.
# Bitmap tests.
# Copyright 2023 Splendide Imaginarius.
# License GPLv2+.
# Test images are from https://github.com/xinntao/Real-ESRGAN/
#
# Run the suite via the "customScript" field in mkxp.json.
# Use RGSS v3 for best results.
def dump(bmp, spr, desc)
spr.bitmap = bmp
Graphics.wait(1)
bmp.to_file("test-results/" + desc + "-lo.png")
if !bmp.hires.nil?
bmp.hires.to_file("test-results/" + desc + "-hi.png")
end
System::puts("Finished " + desc)
end
# Setup graphics
Graphics.resize_screen(640, 480)
# Setup font
fnt = Font.new("Liberation Sans", 100)
# Setup splash screen
bmp = Bitmap.new(640, 480)
bmp.fill_rect(0, 0, 640, 480, Color.new(0, 0, 0))
bmp.font = fnt
bmp.draw_text(0, 0, 640, 240, "High-Res Test Suite", 1)
bmp.draw_text(0, 240, 640, 240, "Starting Now", 1)
spr = Sprite.new()
spr.bitmap = bmp
Graphics.wait(1 * 60)
# Tests start here
bmp = Bitmap.new("Graphics/Pictures/children-alpha")
dump(bmp, spr, "constructor-filename")
# TODO: Filename GIF constructor
bmp = Bitmap.new(640, 480)
bmp.clear
dump(bmp, spr, "constructor-dimensions")
# TODO: Animation constructor
bmp = Bitmap.new("Graphics/Pictures/tree_alpha_16bit")
bmp.clear
bmp2 = Bitmap.new("Graphics/Pictures/children-alpha-lo")
bmp.stretch_blt(bmp.rect, bmp2, bmp2.rect)
dump(bmp, spr, "stretch-blt-clear-tree-lo-children-full-opaque")
bmp = Bitmap.new("Graphics/Pictures/tree_alpha_16bit")
bmp.clear
bmp2 = Bitmap.new("Graphics/Pictures/children-alpha-lo")
bmp.stretch_blt(bmp.rect, bmp2, bmp2.rect, 127)
dump(bmp, spr, "stretch-blt-clear-tree-lo-children-full-semitransparent")
bmp = Bitmap.new("Graphics/Pictures/tree_alpha_16bit")
bmp.clear
rect = bmp.rect
rect.width /= 2
rect.height /= 2
rect.x = rect.width
rect.y = rect.height
bmp2 = Bitmap.new("Graphics/Pictures/children-alpha-lo")
rect2 = bmp2.rect
rect2.width /= 2
rect2.height /= 2
rect2.x = rect2.width
bmp.stretch_blt(rect, bmp2, rect2, 127)
dump(bmp, spr, "stretch-blt-clear-tree-lo-children-quarter-semitransparent")
bmp = Bitmap.new("Graphics/Pictures/tree_alpha_16bit")
bmp.clear
bmp2 = Bitmap.new("Graphics/Pictures/children-alpha")
bmp.stretch_blt(bmp.rect, bmp2, bmp2.rect)
dump(bmp, spr, "stretch-blt-clear-tree-hi-children-full-opaque")
bmp = Bitmap.new("Graphics/Pictures/tree_alpha_16bit")
bmp.clear
bmp2 = Bitmap.new("Graphics/Pictures/children-alpha")
bmp.stretch_blt(bmp.rect, bmp2, bmp2.rect, 127)
dump(bmp, spr, "stretch-blt-clear-tree-hi-children-full-semitransparent")
bmp = Bitmap.new("Graphics/Pictures/tree_alpha_16bit")
bmp.clear
rect = bmp.rect
rect.width /= 2
rect.height /= 2
rect.x = rect.width
rect.y = rect.height
bmp2 = Bitmap.new("Graphics/Pictures/children-alpha")
rect2 = bmp2.rect
rect2.width /= 2
rect2.height /= 2
rect2.x = rect2.width
bmp.stretch_blt(rect, bmp2, rect2, 127)
dump(bmp, spr, "stretch-blt-clear-tree-hi-children-quarter-semitransparent")
bmp = Bitmap.new("Graphics/Pictures/tree_alpha_16bit")
bmp.fill_rect(bmp.rect, Color.new(0, 0, 0))
bmp2 = Bitmap.new("Graphics/Pictures/children-alpha-lo")
bmp.stretch_blt(bmp.rect, bmp2, bmp2.rect)
dump(bmp, spr, "stretch-blt-black-tree-lo-children-full-opaque")
bmp = Bitmap.new("Graphics/Pictures/tree_alpha_16bit")
bmp.fill_rect(bmp.rect, Color.new(0, 0, 0))
bmp2 = Bitmap.new("Graphics/Pictures/children-alpha-lo")
bmp.stretch_blt(bmp.rect, bmp2, bmp2.rect, 127)
dump(bmp, spr, "stretch-blt-black-tree-lo-children-full-semitransparent")
bmp = Bitmap.new("Graphics/Pictures/tree_alpha_16bit")
bmp.fill_rect(bmp.rect, Color.new(0, 0, 0))
rect = bmp.rect
rect.width /= 2
rect.height /= 2
rect.x = rect.width
rect.y = rect.height
bmp2 = Bitmap.new("Graphics/Pictures/children-alpha-lo")
rect2 = bmp2.rect
rect2.width /= 2
rect2.height /= 2
rect2.x = rect2.width
bmp.stretch_blt(rect, bmp2, rect2, 127)
dump(bmp, spr, "stretch-blt-black-tree-lo-children-quarter-semitransparent")
bmp = Bitmap.new("Graphics/Pictures/tree_alpha_16bit")
bmp.fill_rect(bmp.rect, Color.new(0, 0, 0))
bmp2 = Bitmap.new("Graphics/Pictures/children-alpha")
bmp.stretch_blt(bmp.rect, bmp2, bmp2.rect)
dump(bmp, spr, "stretch-blt-black-tree-hi-children-full-opaque")
bmp = Bitmap.new("Graphics/Pictures/tree_alpha_16bit")
bmp.fill_rect(bmp.rect, Color.new(0, 0, 0))
bmp2 = Bitmap.new("Graphics/Pictures/children-alpha")
bmp.stretch_blt(bmp.rect, bmp2, bmp2.rect, 127)
dump(bmp, spr, "stretch-blt-black-tree-hi-children-full-semitransparent")
bmp = Bitmap.new("Graphics/Pictures/tree_alpha_16bit")
bmp.fill_rect(bmp.rect, Color.new(0, 0, 0))
rect = bmp.rect
rect.width /= 2
rect.height /= 2
rect.x = rect.width
rect.y = rect.height
bmp2 = Bitmap.new("Graphics/Pictures/children-alpha")
rect2 = bmp2.rect
rect2.width /= 2
rect2.height /= 2
rect2.x = rect2.width
bmp.stretch_blt(rect, bmp2, rect2, 127)
dump(bmp, spr, "stretch-blt-black-tree-hi-children-quarter-semitransparent")
bmp = Bitmap.new("Graphics/Pictures/tree_alpha_16bit-lo")
bmp2 = Bitmap.new("Graphics/Pictures/children-alpha-lo")
bmp.stretch_blt(bmp.rect, bmp2, bmp2.rect)
dump(bmp, spr, "stretch-blt-lo-tree-lo-children-full-opaque")
bmp = Bitmap.new("Graphics/Pictures/tree_alpha_16bit-lo")
bmp2 = Bitmap.new("Graphics/Pictures/children-alpha-lo")
bmp.stretch_blt(bmp.rect, bmp2, bmp2.rect, 127)
dump(bmp, spr, "stretch-blt-lo-tree-lo-children-full-semitransparent")
bmp = Bitmap.new("Graphics/Pictures/tree_alpha_16bit-lo")
rect = bmp.rect
rect.width /= 2
rect.height /= 2
rect.x = rect.width
rect.y = rect.height
bmp2 = Bitmap.new("Graphics/Pictures/children-alpha-lo")
rect2 = bmp2.rect
rect2.width /= 2
rect2.height /= 2
rect2.x = rect2.width
bmp.stretch_blt(rect, bmp2, rect2, 127)
dump(bmp, spr, "stretch-blt-lo-tree-lo-children-quarter-semitransparent")
bmp = Bitmap.new("Graphics/Pictures/tree_alpha_16bit-lo")
bmp2 = Bitmap.new("Graphics/Pictures/children-alpha")
bmp.stretch_blt(bmp.rect, bmp2, bmp2.rect)
dump(bmp, spr, "stretch-blt-lo-tree-hi-children-full-opaque")
bmp = Bitmap.new("Graphics/Pictures/tree_alpha_16bit-lo")
bmp2 = Bitmap.new("Graphics/Pictures/children-alpha")
bmp.stretch_blt(bmp.rect, bmp2, bmp2.rect, 127)
dump(bmp, spr, "stretch-blt-lo-tree-hi-children-full-semitransparent")
bmp = Bitmap.new("Graphics/Pictures/tree_alpha_16bit-lo")
rect = bmp.rect
rect.width /= 2
rect.height /= 2
rect.x = rect.width
rect.y = rect.height
bmp2 = Bitmap.new("Graphics/Pictures/children-alpha")
rect2 = bmp2.rect
rect2.width /= 2
rect2.height /= 2
rect2.x = rect2.width
bmp.stretch_blt(rect, bmp2, rect2, 127)
dump(bmp, spr, "stretch-blt-lo-tree-hi-children-quarter-semitransparent")
bmp = Bitmap.new("Graphics/Pictures/tree_alpha_16bit")
bmp2 = Bitmap.new("Graphics/Pictures/children-alpha-lo")
bmp.stretch_blt(bmp.rect, bmp2, bmp2.rect)
dump(bmp, spr, "stretch-blt-hi-tree-lo-children-full-opaque")
bmp = Bitmap.new("Graphics/Pictures/tree_alpha_16bit")
bmp2 = Bitmap.new("Graphics/Pictures/children-alpha-lo")
bmp.stretch_blt(bmp.rect, bmp2, bmp2.rect, 127)
dump(bmp, spr, "stretch-blt-hi-tree-lo-children-full-semitransparent")
bmp = Bitmap.new("Graphics/Pictures/tree_alpha_16bit")
rect = bmp.rect
rect.width /= 2
rect.height /= 2
rect.x = rect.width
rect.y = rect.height
bmp2 = Bitmap.new("Graphics/Pictures/children-alpha-lo")
rect2 = bmp2.rect
rect2.width /= 2
rect2.height /= 2
rect2.x = rect2.width
bmp.stretch_blt(rect, bmp2, rect2, 127)
dump(bmp, spr, "stretch-blt-hi-tree-lo-children-quarter-semitransparent")
bmp = Bitmap.new("Graphics/Pictures/tree_alpha_16bit")
bmp2 = Bitmap.new("Graphics/Pictures/children-alpha")
bmp.stretch_blt(bmp.rect, bmp2, bmp2.rect)
dump(bmp, spr, "stretch-blt-hi-tree-hi-children-full-opaque")
bmp = Bitmap.new("Graphics/Pictures/tree_alpha_16bit")
bmp2 = Bitmap.new("Graphics/Pictures/children-alpha")
bmp.stretch_blt(bmp.rect, bmp2, bmp2.rect, 127)
dump(bmp, spr, "stretch-blt-hi-tree-hi-children-full-semitransparent")
bmp = Bitmap.new("Graphics/Pictures/tree_alpha_16bit")
rect = bmp.rect
rect.width /= 2
rect.height /= 2
rect.x = rect.width
rect.y = rect.height
bmp2 = Bitmap.new("Graphics/Pictures/children-alpha")
rect2 = bmp2.rect
rect2.width /= 2
rect2.height /= 2
rect2.x = rect2.width
bmp.stretch_blt(rect, bmp2, rect2, 127)
dump(bmp, spr, "stretch-blt-hi-tree-hi-children-quarter-semitransparent")
bmp = Bitmap.new(640, 480)
bmp.fill_rect(100, 200, 450, 300, Color.new(0, 0, 0))
bmp.fill_rect(50, 100, 220, 150, Color.new(255, 0, 0))
dump(bmp, spr, "fill-rect")
bmp = Bitmap.new(640, 480)
bmp.gradient_fill_rect(100, 200, 450, 300, Color.new(0, 0, 0), Color.new(0, 0, 255))
bmp.gradient_fill_rect(50, 100, 220, 150, Color.new(255, 0, 0), Color.new(255, 255, 0))
dump(bmp, spr, "gradient-fill-rect-horizontal")
bmp = Bitmap.new(640, 480)
bmp.gradient_fill_rect(100, 200, 450, 300, Color.new(0, 0, 0), Color.new(0, 0, 255), true)
bmp.gradient_fill_rect(50, 100, 220, 150, Color.new(255, 0, 0), Color.new(255, 255, 0), true)
dump(bmp, spr, "gradient-fill-rect-vertical")
bmp = Bitmap.new("Graphics/Pictures/children-alpha-lo")
bmp.clear_rect(300, 175, 100, 150)
dump(bmp, spr, "clear-rect-lo-children")
bmp = Bitmap.new("Graphics/Pictures/children-alpha")
bmp.clear_rect(300, 175, 100, 150)
dump(bmp, spr, "clear-rect-hi-children")
# TODO: linear-blur is arguably passing but maybe should have stronger blur?
bmp = Bitmap.new("Graphics/Pictures/children-alpha")
bmp.blur
dump(bmp, spr, "linear-blur")
bmp = Bitmap.new("Graphics/Pictures/children-alpha-lo")
bmp.radial_blur(0, 10)
dump(bmp, spr, "radial-blur-0-lo-children")
bmp = Bitmap.new("Graphics/Pictures/children-alpha")
bmp.radial_blur(0, 10)
dump(bmp, spr, "radial-blur-0-hi-children")
bmp = Bitmap.new("Graphics/Pictures/children-alpha-lo")
bmp.radial_blur(3, 10)
dump(bmp, spr, "radial-blur-3-lo-children")
bmp = Bitmap.new("Graphics/Pictures/children-alpha")
bmp.radial_blur(3, 10)
dump(bmp, spr, "radial-blur-3-hi-children")
bmp = Bitmap.new("Graphics/Pictures/children-alpha")
bmp.clear
dump(bmp, spr, "clear-full")
bmp2 = Bitmap.new("Graphics/Pictures/children-alpha")
bmp = Bitmap.new(bmp2.width, bmp2.height)
for x in (0...bmp2.width)
for y in (0...bmp2.height)
bmp.set_pixel(x, y, bmp2.get_pixel(x, y))
end
end
dump(bmp, spr, "get-set-pixel-dimensions")
bmp2 = Bitmap.new("Graphics/Pictures/children-alpha")
bmp = Bitmap.new("Graphics/Pictures/children-alpha")
bmp.clear
for x in (0...bmp2.width)
for y in (0...bmp2.height)
bmp.set_pixel(x, y, bmp2.get_pixel(x, y))
end
end
dump(bmp, spr, "get-set-pixel-clear")
bmp2 = Bitmap.new("Graphics/Pictures/children-alpha")
bmp = Bitmap.new("Graphics/Pictures/children-alpha")
bmp.hires.clear
for x in (0...bmp2.hires.width)
for y in (0...bmp2.hires.height)
bmp.hires.set_pixel(x, y, bmp2.hires.get_pixel(x, y))
end
end
dump(bmp, spr, "get-set-pixel-direct")
bmp = Bitmap.new("Graphics/Pictures/children-alpha-lo")
bmp.hue_change(180)
dump(bmp, spr, "hue-change-lo-children")
bmp = Bitmap.new("Graphics/Pictures/children-alpha")
bmp.hue_change(180)
dump(bmp, spr, "hue-change-hi-children")
bmp = Bitmap.new(640, 480)
fnt = Font.new("Liberation Sans", 100)
bmp.font = fnt
bmp.draw_text(100, 200, 450, 300, "We <3 Real-ESRGAN", 1)
dump(bmp, spr, "draw-text-plain")
bmp = Bitmap.new(640, 480)
fnt = Font.new("Liberation Sans", 15)
bmp.font = fnt
bmp.draw_text(100, 200, 450, 300, "We <3 Real-ESRGAN", 0)
dump(bmp, spr, "draw-text-left")
bmp = Bitmap.new(640, 480)
fnt = Font.new("Liberation Sans", 15)
bmp.font = fnt
bmp.draw_text(100, 200, 450, 300, "We <3 Real-ESRGAN", 2)
dump(bmp, spr, "draw-text-right")
bmp = Bitmap.new(640, 480)
fnt = Font.new("Liberation Sans", 100)
fnt.bold = true
bmp.font = fnt
bmp.draw_text(100, 200, 450, 300, "We <3 Real-ESRGAN", 1)
dump(bmp, spr, "draw-text-bold")
bmp = Bitmap.new(640, 480)
fnt = Font.new("Liberation Sans", 100)
fnt.italic = true
bmp.font = fnt
bmp.draw_text(100, 200, 450, 300, "We <3 Real-ESRGAN", 1)
dump(bmp, spr, "draw-text-italic")
bmp = Bitmap.new(640, 480)
fnt = Font.new("Liberation Sans", 100)
fnt.color = Color.new(255, 0, 0)
fnt.outline = false
bmp.font = fnt
bmp.draw_text(100, 200, 450, 300, "We <3 Real-ESRGAN", 1)
dump(bmp, spr, "draw-text-red-no-outline")
bmp = Bitmap.new(640, 480)
fnt = Font.new("Liberation Sans", 100)
fnt.color = Color.new(255, 127, 127)
fnt.shadow = true
bmp.font = fnt
bmp.draw_text(100, 200, 450, 300, "We <3 Real-ESRGAN", 1)
dump(bmp, spr, "draw-text-pink-shadow")
bmp = Bitmap.new(640, 480)
fnt = Font.new("Liberation Sans", 100)
fnt.out_color = Color.new(0, 255, 0)
bmp.font = fnt
bmp.draw_text(100, 200, 450, 300, "We <3 Real-ESRGAN", 1)
dump(bmp, spr, "draw-text-green-outline")
# TODO: Animation tests, if we can find a good way to test them.
# Tests are finished, show exit screen
bmp = Bitmap.new(640, 480)
bmp.fill_rect(0, 0, 640, 480, Color.new(0, 0, 0))
fnt = Font.new("Liberation Sans", 100)
bmp.font = fnt
bmp.draw_text(0, 0, 640, 240, "High-Res Test Suite", 1)
bmp.draw_text(0, 240, 640, 240, "Has Finished", 1)
spr.bitmap = bmp
Graphics.wait(1 * 60)
exit

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,82 @@
# Test suite for mkxp-z high-res Bitmap replacement.
# Sprite tests.
# Copyright 2023 Splendide Imaginarius.
# License GPLv2+.
# Test images are from https://github.com/xinntao/Real-ESRGAN/
#
# Run the suite via the "customScript" field in mkxp.json.
# Use RGSS v3 for best results.
def dump2(bmp, spr, desc)
spr.bitmap = bmp
Graphics.wait(1)
#Graphics.wait(5*60)
#Graphics.screenshot("test-results/" + desc + ".png")
shot = Graphics.snap_to_bitmap
shot.to_file("test-results/" + desc + "-lo.png")
if !shot.hires.nil?
shot.hires.to_file("test-results/" + desc + "-hi.png")
end
System::puts("Finished " + desc)
end
def dump(bmp, spr, desc)
spr.viewport = nil
dump2(bmp, spr, desc + "-direct")
spr.tone.gray = 128
dump2(bmp, spr, desc + "-directtonegray")
spr.tone.gray = 0
$vp.ox = 0
spr.viewport = $vp
dump2(bmp, spr, desc + "-viewport")
$vp.ox = 250
dump2(bmp, spr, desc + "-viewportshift")
$vp.ox = 0
$vp.rect.width = 320
dump2(bmp, spr, desc + "-viewportsquash")
$vp.rect.width = 640
$vp.tone.green = -128
dump2(bmp, spr, desc + "-viewporttonegreen")
$vp.tone.green = 0
$vp.tone.gray = 128
dump2(bmp, spr, desc + "-viewporttonegray")
$vp.tone.gray = 0
end
# Setup graphics
Graphics.resize_screen(448, 640)
$vp = Viewport.new()
spr = Sprite.new()
bmp = Bitmap.new("Graphics/Pictures/OST_009-Small")
spr.zoom_x = 1.0
spr.zoom_y = 1.0
dump(bmp, spr, "Small")
bmp = Bitmap.new("Graphics/Pictures/OST_009-Big")
spr.zoom_x = 448.0 / 1792.0
spr.zoom_y = 448.0 / 1792.0
dump(bmp, spr, "Big")
bmp = Bitmap.new("Graphics/Pictures/OST_009")
spr.zoom_x = 1.0
spr.zoom_y = 1.0
dump(bmp, spr, "Substituted")
bmp = Bitmap.new("Graphics/Pictures/OST_009")
spr.zoom_x = 1.5
spr.zoom_y = 1.5
dump(bmp, spr, "Substituted-Zoomed")
bmp = Bitmap.new("Graphics/Pictures/OST_009")
spr.zoom_x = 448.0 / 1792.0
spr.zoom_y = 448.0 / 1792.0
dump(bmp.hires, spr, "Substituted-Explicit")
# Test for null pointer
spr_null = Sprite.new()
spr_null.src_rect = Rect.new(0, 0, 448, 640)
exit