From 78f3d81b5028e1bd8edce2a79c8b8f92730855d7 Mon Sep 17 00:00:00 2001 From: jairus Date: Sun, 12 Apr 2026 12:34:21 +0300 Subject: [PATCH 1/5] downscale preview image for faster save --- game/mainwindow.cpp | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/game/mainwindow.cpp b/game/mainwindow.cpp index f58339f41..c2f76fb09 100644 --- a/game/mainwindow.cpp +++ b/game/mainwindow.cpp @@ -28,6 +28,34 @@ using namespace Tempest; +namespace { + Pixmap downscaleSavePreviewPixmap(Pixmap src) { + if(src.isEmpty() || src.format()!=TextureFormat::RGBA8) + return src; + constexpr uint32_t kThumbW = 640, kThumbH = 480; + const uint32_t w = src.w(), h = src.h(); + + if(w<=kThumbW || h<=kThumbH) + return src; + + Pixmap out(kThumbW, kThumbH, TextureFormat::RGBA8); + const auto* s = static_cast(src.data()); + auto* d = static_cast(out.data()); + + // Copy source pixel map to reduced pixel map using Nearest-Neighbor interpolation + for(uint32_t y=0; y(tex)); + + // reduce size of the save entry preview screenshot for faster save & load + auto pm = downscaleSavePreviewPixmap(device.readPixels(textureCast(tex))); if(dialogs.isActive()) return; From 8e4dfb431a81e4c00ac39980866378a8f0d3a039 Mon Sep 17 00:00:00 2001 From: jairus Date: Mon, 13 Apr 2026 07:46:09 +0300 Subject: [PATCH 2/5] preserve aspect ratio and original texture format --- game/mainwindow.cpp | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/game/mainwindow.cpp b/game/mainwindow.cpp index c2f76fb09..6d8614666 100644 --- a/game/mainwindow.cpp +++ b/game/mainwindow.cpp @@ -30,26 +30,30 @@ using namespace Tempest; namespace { Pixmap downscaleSavePreviewPixmap(Pixmap src) { - if(src.isEmpty() || src.format()!=TextureFormat::RGBA8) + if(src.isEmpty()) return src; - constexpr uint32_t kThumbW = 640, kThumbH = 480; + + constexpr uint32_t kThumbW = 1024; const uint32_t w = src.w(), h = src.h(); - - if(w<=kThumbW || h<=kThumbH) + if(w <= kThumbW) return src; - Pixmap out(kThumbW, kThumbH, TextureFormat::RGBA8); - const auto* s = static_cast(src.data()); - auto* d = static_cast(out.data()); + const uint32_t nh = std::max(1u, uint32_t((size_t(h) * kThumbW) / w)); + const size_t bpp = src.bpp(); + + Pixmap out(kThumbW, nh, src.format()); + const uint8_t* s = static_cast(src.data()); + uint8_t* d = static_cast(out.data()); // Copy source pixel map to reduced pixel map using Nearest-Neighbor interpolation - for(uint32_t y=0; y Date: Sat, 18 Apr 2026 22:41:54 +0200 Subject: [PATCH 3/5] gpu based downscale --- game/graphics/shaders.cpp | 13 ++++---- game/graphics/shaders.h | 2 +- game/mainwindow.cpp | 67 ++++++++++++++++++--------------------- shader/CMakeLists.txt | 1 + shader/downscale.frag | 22 +++++++++++++ 5 files changed, 61 insertions(+), 44 deletions(-) create mode 100644 shader/downscale.frag diff --git a/game/graphics/shaders.cpp b/game/graphics/shaders.cpp index 2c72a458b..f4c0daec3 100644 --- a/game/graphics/shaders.cpp +++ b/game/graphics/shaders.cpp @@ -42,7 +42,8 @@ Shaders& Shaders::inst() { } void Shaders::compileKeyShaders() { - bink = postEffect("bink"); + bink = postEffect("bink"); + downscale = postEffect("downscale"); } void Shaders::compileShaders() { @@ -50,13 +51,13 @@ void Shaders::compileShaders() { const bool meshlets = Gothic::options().doMeshShading; - copyBuf = computeShader("copy.comp.sprv"); - copyImg = computeShader("copy_img.comp.sprv"); - copy = postEffect("copy"); + copyBuf = computeShader("copy.comp.sprv"); + copyImg = computeShader("copy_img.comp.sprv"); + copy = postEffect("copy"); - patch = computeShader("patch.comp.sprv"); + patch = computeShader("patch.comp.sprv"); - stash = postEffect("stash"); + stash = postEffect("stash"); clusterInit = computeShader("cluster_init.comp.sprv"); clusterPatch = computeShader("cluster_patch.comp.sprv"); diff --git a/game/graphics/shaders.h b/game/graphics/shaders.h index 9f113e5c2..fe9c77ba6 100644 --- a/game/graphics/shaders.h +++ b/game/graphics/shaders.h @@ -35,7 +35,7 @@ class Shaders { Tempest::ComputePipeline copyBuf; Tempest::ComputePipeline copyImg; Tempest::ComputePipeline patch; - Tempest::RenderPipeline copy; + Tempest::RenderPipeline copy, downscale; Tempest::RenderPipeline stash; Tempest::RenderPipeline bink; diff --git a/game/mainwindow.cpp b/game/mainwindow.cpp index 0281df87f..42210c211 100644 --- a/game/mainwindow.cpp +++ b/game/mainwindow.cpp @@ -28,38 +28,6 @@ using namespace Tempest; -namespace { - Pixmap downscaleSavePreviewPixmap(Pixmap src) { - if(src.isEmpty()) - return src; - - constexpr uint32_t kThumbW = 1024; - const uint32_t w = src.w(), h = src.h(); - if(w <= kThumbW) - return src; - - const uint32_t nh = std::max(1u, uint32_t((size_t(h) * kThumbW) / w)); - const size_t bpp = src.bpp(); - - Pixmap out(kThumbW, nh, src.format()); - const uint8_t* s = static_cast(src.data()); - uint8_t* d = static_cast(out.data()); - - // Copy source pixel map to reduced pixel map using Nearest-Neighbor interpolation - for(uint32_t y=0; y(tex))); - if(dialogs.isActive()) return; if(auto w = Gothic::inst().world(); w!=nullptr && w->currentCs()!=nullptr) return; + auto tex = renderer.screenshoot(cmdId); + auto lres = Attachment(); + + static int32_t kThumbW = 800; + const int32_t kThumbH = tex.w()>0 ? int32_t((tex.h() * kThumbW) / tex.w()) : 0; + if(kThumbW>0 && kThumbH>0 && kThumbW(thumb)); + Gothic::inst().startSave(std::move(textureCast(tex)),[slot=std::string(slot),name=std::string(name),pm](std::unique_ptr&& game){ if(!game) return std::move(game); diff --git a/shader/CMakeLists.txt b/shader/CMakeLists.txt index 4eda6621a..f58d076d2 100644 --- a/shader/CMakeLists.txt +++ b/shader/CMakeLists.txt @@ -197,6 +197,7 @@ add_shader(item.frag inventory/item.frag) add_shader(stash.frag stash.frag) add_shader(bink.frag bink/bink.frag) +add_shader(downscale.frag downscale.frag) add_shader(direct_light.frag lighting/direct_light.frag) add_shader(direct_light_sh.frag lighting/direct_light.frag -DSHADOW_MAP) diff --git a/shader/downscale.frag b/shader/downscale.frag new file mode 100644 index 000000000..9f63d3964 --- /dev/null +++ b/shader/downscale.frag @@ -0,0 +1,22 @@ +#version 450 + +layout(binding = 0) uniform sampler2D src; +layout(location = 0) out vec4 outColor; + +layout(push_constant, std140) uniform Push { + ivec2 dstSize; + }; + +void main() { + ivec2 srcSize = textureSize(src, 0); + + ivec2 tl = (ivec2(gl_FragCoord.xy+ivec2(0))*srcSize)/dstSize; + ivec2 br = (ivec2(gl_FragCoord.xy+ivec2(1))*srcSize)/dstSize; + + vec4 color = vec4(0); + for(int i=tl.x; i Date: Sat, 18 Apr 2026 22:42:16 +0200 Subject: [PATCH 4/5] use jpg for save-game priviews --- game/game/gamesession.cpp | 4 ++-- game/game/serialize.cpp | 6 +++++- game/game/serialize.h | 3 +++ game/ui/gamemenu.cpp | 2 ++ 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/game/game/gamesession.cpp b/game/game/gamesession.cpp index a3fc1676a..6f5c5492e 100644 --- a/game/game/gamesession.cpp +++ b/game/game/gamesession.cpp @@ -196,8 +196,8 @@ void GameSession::save(Serialize &fout, std::string_view name, const Pixmap& scr fout.write(i.name); } - fout.setEntry("preview.png"); - fout.write(screen); + fout.setEntry("preview.jpg"); + fout.write(std::tie(screen,"jpg")); fout.setEntry("game/session"); fout.write(ticks,wrldTime,wrldTimePart,wrld->name()); diff --git a/game/game/serialize.cpp b/game/game/serialize.cpp index b1e6b54e2..c3422b3a0 100644 --- a/game/game/serialize.cpp +++ b/game/game/serialize.cpp @@ -273,10 +273,14 @@ void Serialize::implRead(SaveGameHeader& p) { } void Serialize::implWrite(const Tempest::Pixmap& p) { + implWrite(p, "png"); + } + +void Serialize::implWrite(const Tempest::Pixmap& p, const char* ext) { std::vector tmp; tmp.reserve(4*1024*1024); Tempest::MemWriter w{tmp}; - p.save(w); + p.save(w, ext); writeBytes(tmp.data(),tmp.size()); } diff --git a/game/game/serialize.h b/game/game/serialize.h index 8d2b5a29d..0ec3d2af1 100644 --- a/game/game/serialize.h +++ b/game/game/serialize.h @@ -250,6 +250,9 @@ class Serialize { void implRead (SaveGameHeader& p); void implWrite(const Tempest::Pixmap& p); + void implWrite(const Tempest::Pixmap& p, const char* ext); + template + void implWrite(std::tuple p) { implWrite(std::get<0>(p), std::get<1>(p)); } void implRead (Tempest::Pixmap& p); void implWrite(const zenkit::INpc& h); diff --git a/game/ui/gamemenu.cpp b/game/ui/gamemenu.cpp index 70f0e142a..61798b47b 100644 --- a/game/ui/gamemenu.cpp +++ b/game/ui/gamemenu.cpp @@ -1012,6 +1012,8 @@ void GameMenu::updateSavTitle(GameMenu::Item& sel) { reader.read(sel.savPriview); // legacy else if(reader.setEntry("preview.png")) reader.read(sel.savPriview); + else if(reader.setEntry("preview.jpg")) + reader.read(sel.savPriview); } catch(std::bad_alloc&) { return; From 57ba6fbce7eb403803c2e1ed39701a610e746016 Mon Sep 17 00:00:00 2001 From: Try Date: Sat, 18 Apr 2026 22:54:26 +0200 Subject: [PATCH 5/5] cleanup --- game/mainwindow.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/game/mainwindow.cpp b/game/mainwindow.cpp index 42210c211..ecc3bccb6 100644 --- a/game/mainwindow.cpp +++ b/game/mainwindow.cpp @@ -1082,14 +1082,12 @@ void MainWindow::saveGame(std::string_view slot, std::string_view name) { CommandBuffer cmd; { auto enc = cmd.startEncoding(device); - if(!lres.isEmpty()) { - enc.setDebugMarker("Downscale screenhoot"); - enc.setFramebuffer({{lres, Vec4(), Tempest::Preserve}}); - enc.setPushData(IVec2(lres.w(), lres.h())); - enc.setBinding(0, tex, Sampler::nearest()); - enc.setPipeline(Shaders::inst().downscale); - enc.draw(nullptr, 0, 3); - } + enc.setDebugMarker("Downscale screenhoot"); + enc.setFramebuffer({{lres, Vec4(), Tempest::Preserve}}); + enc.setPushData(IVec2(lres.w(), lres.h())); + enc.setBinding(0, tex, Sampler::nearest()); + enc.setPipeline(Shaders::inst().downscale); + enc.draw(nullptr, 0, 3); } auto sync = device.submit(cmd); sync.wait();