diff --git a/binding/bitmap-binding.cpp b/binding/bitmap-binding.cpp index ee77646c..5af6deb4 100644 --- a/binding/bitmap-binding.cpp +++ b/binding/bitmap-binding.cpp @@ -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); diff --git a/binding/graphics-binding.cpp b/binding/graphics-binding.cpp index 75095c2c..6873f35e 100644 --- a/binding/graphics-binding.cpp +++ b/binding/graphics-binding.cpp @@ -19,6 +19,7 @@ ** along with mkxp. If not, see . */ +#include "config.h" #include "graphics.h" #include "sharedstate.h" #include "binding-util.h" diff --git a/mkxp.json b/mkxp.json index afa41c51..8a3649c7 100644 --- a/mkxp.json +++ b/mkxp.json @@ -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) // diff --git a/src/config.cpp b/src/config.cpp index 9048c6d7..58932abc 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -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); diff --git a/src/config.h b/src/config.h index ade1e64f..328df348 100644 --- a/src/config.h +++ b/src/config.h @@ -45,6 +45,10 @@ struct Config { bool fixedAspectRatio; bool smoothScaling; bool lanczos3Scaling; + bool enableHires; + double textureScalingFactor; + double framebufferScalingFactor; + double atlasScalingFactor; bool vsync; int defScreenW; diff --git a/src/display/bitmap.cpp b/src/display/bitmap.cpp index e97252b7..847539f1 100644 --- a/src/display/bitmap.cpp +++ b/src/display/bitmap.cpp @@ -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(angle, 0, 359); divisions = clamp(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(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 &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) { diff --git a/src/display/bitmap.h b/src/display/bitmap.h index 6ca72c13..44760bb6 100644 --- a/src/display/bitmap.h +++ b/src/display/bitmap.h @@ -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"; } diff --git a/src/display/font.cpp b/src/display/font.cpp index 98d4033e..d0aa8d4b 100644 --- a/src/display/font.cpp +++ b/src/display/font.cpp @@ -399,14 +399,17 @@ void Font::setName(const std::vector &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; diff --git a/src/display/font.h b/src/display/font.h index f76b8421..fe7d9751 100644 --- a/src/display/font.h +++ b/src/display/font.h @@ -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& ) diff --git a/src/display/gl/gl-meta.cpp b/src/display/gl/gl-meta.cpp index 1f39dc8d..f80050c1 100644 --- a/src/display/gl/gl-meta.cpp +++ b/src/display/gl/gl-meta.cpp @@ -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(); + } } } diff --git a/src/display/gl/gl-meta.h b/src/display/gl/gl-meta.h index fd8cf8ab..13ef1ffd 100644 --- a/src/display/gl/gl-meta.h +++ b/src/display/gl/gl-meta.h @@ -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); diff --git a/src/display/gl/gl-util.h b/src/display/gl/gl-util.h index 945af787..212040e2 100644 --- a/src/display/gl/gl-util.h +++ b/src/display/gl/gl-util.h @@ -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 diff --git a/src/display/gl/glstate.cpp b/src/display/gl/glstate.cpp index 6bf78d74..d021d887 100644 --- a/src/display/gl/glstate.cpp +++ b/src/display/gl/glstate.cpp @@ -23,7 +23,9 @@ #include "config.h" #include "etc.h" #include "gl-fun.h" +#include "graphics.h" #include "shader.h" +#include "sharedstate.h" #include @@ -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) { diff --git a/src/display/gl/quad.h b/src/display/gl/quad.h index d200c6b5..ecff83e4 100644 --- a/src/display/gl/quad.h +++ b/src/display/gl/quad.h @@ -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" diff --git a/src/display/gl/shader.cpp b/src/display/gl/shader.cpp index 53aea311..28bb2ed0 100644 --- a/src/display/gl/shader.cpp +++ b/src/display/gl/shader.cpp @@ -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); diff --git a/src/display/gl/shader.h b/src/display/gl/shader.h index aeec2cfb..fd79dfbc 100644 --- a/src/display/gl/shader.h +++ b/src/display/gl/shader.h @@ -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; }; diff --git a/src/display/gl/tileatlasvx.cpp b/src/display/gl/tileatlasvx.cpp index 116fc305..aa6a78f8 100644 --- a/src/display/gl/tileatlasvx.cpp +++ b/src/display/gl/tileatlasvx.cpp @@ -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])) \ { \ diff --git a/src/display/graphics.cpp b/src/display/graphics.cpp index 366fe980..2a9fe935 100644 --- a/src/display/graphics.cpp +++ b/src/display/graphics.cpp @@ -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 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); diff --git a/src/display/graphics.h b/src/display/graphics.h index 90bcd74a..130478f3 100644 --- a/src/display/graphics.h +++ b/src/display/graphics.h @@ -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); diff --git a/src/display/sprite.cpp b/src/display/sprite.cpp index 5c76297d..5b7a3766 100644 --- a/src/display/sprite.cpp +++ b/src/display/sprite.cpp @@ -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); diff --git a/src/display/tilemap.cpp b/src/display/tilemap.cpp index 2cd30c46..bc2da7df 100644 --- a/src/display/tilemap.cpp +++ b/src/display/tilemap.cpp @@ -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()); diff --git a/src/display/tilemapvx.cpp b/src/display/tilemapvx.cpp index 6dfe2849..678f1a60 100644 --- a/src/display/tilemapvx.cpp +++ b/src/display/tilemapvx.cpp @@ -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(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, diff --git a/tests/hires-bitmap/Graphics/Pictures/children-alpha-lo.jxl b/tests/hires-bitmap/Graphics/Pictures/children-alpha-lo.jxl new file mode 100644 index 00000000..69fa96e3 Binary files /dev/null and b/tests/hires-bitmap/Graphics/Pictures/children-alpha-lo.jxl differ diff --git a/tests/hires-bitmap/Graphics/Pictures/children-alpha.jxl b/tests/hires-bitmap/Graphics/Pictures/children-alpha.jxl new file mode 100644 index 00000000..69fa96e3 Binary files /dev/null and b/tests/hires-bitmap/Graphics/Pictures/children-alpha.jxl differ diff --git a/tests/hires-bitmap/Graphics/Pictures/tree_alpha_16bit-lo.jxl b/tests/hires-bitmap/Graphics/Pictures/tree_alpha_16bit-lo.jxl new file mode 100644 index 00000000..f2640c59 Binary files /dev/null and b/tests/hires-bitmap/Graphics/Pictures/tree_alpha_16bit-lo.jxl differ diff --git a/tests/hires-bitmap/Graphics/Pictures/tree_alpha_16bit.jxl b/tests/hires-bitmap/Graphics/Pictures/tree_alpha_16bit.jxl new file mode 100644 index 00000000..f2640c59 Binary files /dev/null and b/tests/hires-bitmap/Graphics/Pictures/tree_alpha_16bit.jxl differ diff --git a/tests/hires-bitmap/Hires/Graphics/Pictures/children-alpha.jxl b/tests/hires-bitmap/Hires/Graphics/Pictures/children-alpha.jxl new file mode 100644 index 00000000..c70c55d8 Binary files /dev/null and b/tests/hires-bitmap/Hires/Graphics/Pictures/children-alpha.jxl differ diff --git a/tests/hires-bitmap/Hires/Graphics/Pictures/tree_alpha_16bit.jxl b/tests/hires-bitmap/Hires/Graphics/Pictures/tree_alpha_16bit.jxl new file mode 100644 index 00000000..bd0ce9d4 Binary files /dev/null and b/tests/hires-bitmap/Hires/Graphics/Pictures/tree_alpha_16bit.jxl differ diff --git a/tests/hires-bitmap/hires-bitmap-test.rb b/tests/hires-bitmap/hires-bitmap-test.rb new file mode 100755 index 00000000..3a62d0f9 --- /dev/null +++ b/tests/hires-bitmap/hires-bitmap-test.rb @@ -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 diff --git a/tests/hires-bitmap/test-results/.RESULTS WILL GO HERE b/tests/hires-bitmap/test-results/.RESULTS WILL GO HERE new file mode 100644 index 00000000..e69de29b diff --git a/tests/hires-sprite/Graphics/Pictures/OST_009-Big.jxl b/tests/hires-sprite/Graphics/Pictures/OST_009-Big.jxl new file mode 100644 index 00000000..d969d7c8 Binary files /dev/null and b/tests/hires-sprite/Graphics/Pictures/OST_009-Big.jxl differ diff --git a/tests/hires-sprite/Graphics/Pictures/OST_009-Small.jxl b/tests/hires-sprite/Graphics/Pictures/OST_009-Small.jxl new file mode 100644 index 00000000..3d55e0ee Binary files /dev/null and b/tests/hires-sprite/Graphics/Pictures/OST_009-Small.jxl differ diff --git a/tests/hires-sprite/Graphics/Pictures/OST_009.jxl b/tests/hires-sprite/Graphics/Pictures/OST_009.jxl new file mode 100644 index 00000000..3d55e0ee Binary files /dev/null and b/tests/hires-sprite/Graphics/Pictures/OST_009.jxl differ diff --git a/tests/hires-sprite/Hires/Graphics/Pictures/OST_009.jxl b/tests/hires-sprite/Hires/Graphics/Pictures/OST_009.jxl new file mode 100644 index 00000000..d969d7c8 Binary files /dev/null and b/tests/hires-sprite/Hires/Graphics/Pictures/OST_009.jxl differ diff --git a/tests/hires-sprite/hires-sprite-test.rb b/tests/hires-sprite/hires-sprite-test.rb new file mode 100755 index 00000000..7c99680b --- /dev/null +++ b/tests/hires-sprite/hires-sprite-test.rb @@ -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 diff --git a/tests/hires-sprite/test-results/.RESULTS WILL GO HERE b/tests/hires-sprite/test-results/.RESULTS WILL GO HERE new file mode 100644 index 00000000..e69de29b