Files
SpartanPort/tests/test_scene_resolution.cpp
Felipe Prado 226fb0e969 Refactor(Branding): renamed the project to SpartanPort (#755)
EN-US

- Renamed code, binaries, packaging, CI and docs to SpartanPort, with the SP monogram and the SpartanPort logo
- Renamed only the names in the shadPS4 core and the FSR patch, keeping authors, dates and licenses
- Added scripts/legacy.py, the only place with the former name, kept for three releases
- Added a one-time copy of the game settings, the launcher settings and the Linux data folder from their former names, without overwriting or deleting anything
- Updated the Linux data move to leave the GPU caches (user/cache) and the run views (out/) in the former folder
- Added forwarding of the former GOW3_ variables to SPARTAN_
- Added the launcher under its former names, so the 2.5.0 updater and existing shortcuts keep working
- Added a note to the READMEs that the project was gow3_pc and that 2.5.0 players keep their saves and settings
- Passed the full Python suite (531 tests, 0 failures) and the Windows zip and Linux AppImage CI builds

---

PT-BR

- Renomeados código, executáveis, empacotamento, CI e docs para SpartanPort, com o monograma SP e o logotipo SpartanPort
- Renomeados só os nomes no núcleo do shadPS4 e no patch do FSR, mantendo autores, datas e licenças
- Adicionado o scripts/legacy.py, único lugar com o nome antigo, mantido por três releases
- Adicionada a cópia única das configurações do jogo, das configurações do launcher e da pasta de dados do Linux a partir dos nomes antigos, sem sobrescrever nem apagar nada
- Atualizada a migração da pasta de dados do Linux para deixar os caches da GPU (user/cache) e as pastas de execução (out/) na pasta antiga
- Adicionado o repasse das variáveis GOW3_ antigas para SPARTAN_
- Adicionado o launcher com os nomes antigos, para que o atualizador da 2.5.0 e os atalhos existentes continuem funcionando
- Adicionada aos READMEs a nota de que o projeto era o gow3_pc e de que jogadores da 2.5.0 mantêm saves e configurações
- Aprovadas a suíte Python completa (531 testes, 0 falhas) e as builds de CI do zip do Windows e da AppImage do Linux
2026-10-19 18:18:13 -03:00

202 lines
11 KiB
C++

// SPDX-License-Identifier: GPL-2.0-or-later
// Exercises the production target/resolve path on Vulkan (Lavapipe works, no game/window).
#include <cassert>
#include <cmath>
#include <cstdio>
#include <cstring>
#include "common/slot_vector.h"
#include "video_core/renderer_vulkan/vk_instance.h"
#include "video_core/renderer_vulkan/vk_runtime.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_scene_resolution.h"
#include "video_core/texture_cache/blit_helper.h"
#include <vk_mem_alloc.h>
int main() {
using namespace Vulkan;
Instance instance(0, false);
// Calls emitted by this executable use a local dispatcher. libspartanportgpu.so initializes
// its own default dispatcher while constructing Instance.
static vk::detail::DynamicLoader loader;
vk::detail::DispatchLoaderDynamic dispatch;
dispatch.init(
loader.getProcAddress<PFN_vkGetInstanceProcAddr>("vkGetInstanceProcAddr"));
dispatch.init(instance.GetInstance());
dispatch.init(instance.GetDevice());
Scheduler scheduler(instance);
Runtime runtime(instance, scheduler);
Common::SlotVector<VideoCore::ImageView> views;
Common::SlotVector<VideoCore::Image> images;
VideoCore::ImageInfo ci;
ci.size = {1920,1080,1};
ci.resources = {1,1};
ci.type = AmdGpu::ImageType::Color2D;
ci.pixel_format = vk::Format::eR8G8B8A8Unorm;
ci.num_bits = 32;
const auto color_id = images.insert(instance, runtime, views, ci);
auto di = ci;
di.pixel_format = vk::Format::eD32SfloatS8Uint;
di.props.is_depth = di.props.has_stencil = true;
const auto depth_id = images.insert(instance, runtime, views, di);
SceneTargets targets(instance, scheduler, runtime, [&](VideoCore::ImageId id, u64 uid) {
if (!images.is_allocated(id)) return static_cast<VideoCore::Image*>(nullptr);
return !uid || images[id].image_uid == uid ? &images[id] : nullptr;
});
const bool color_eligible = targets.Eligible(images[color_id]);
const bool depth_eligible = targets.Eligible(images[depth_id]);
std::fprintf(stderr, "Scene target eligibility: color=%d depth=%d\n",
color_eligible, depth_eligible);
if (!color_eligible || !depth_eligible) {
std::puts("Scene targets: unsupported format blit, live scaling skipped");
return 0;
}
VkBuffer readback{};
VmaAllocation allocation{};
VmaAllocationInfo ai{};
const VkBufferCreateInfo bi{.sType=VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.size=16, .usage=VK_BUFFER_USAGE_TRANSFER_DST_BIT};
const VmaAllocationCreateInfo ac{.flags=VMA_ALLOCATION_CREATE_MAPPED_BIT |
VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT, .usage=VMA_MEMORY_USAGE_AUTO,
.requiredFlags=VK_MEMORY_PROPERTY_HOST_COHERENT_BIT};
assert(vmaCreateBuffer(instance.GetAllocator(), &bi, &ac, &readback, &allocation, &ai) == VK_SUCCESS);
const auto pixel = [&](vk::Image image, vk::ImageAspectFlagBits aspect,
vk::ImageLayout layout, u32 w, u32 h) {
scheduler.EndRendering();
const auto cmd = scheduler.CommandBuffer();
const vk::MemoryBarrier2 b{.srcStageMask=vk::PipelineStageFlagBits2::eAllCommands,
.srcAccessMask=vk::AccessFlagBits2::eMemoryWrite,
.dstStageMask=vk::PipelineStageFlagBits2::eTransfer,
.dstAccessMask=vk::AccessFlagBits2::eTransferRead};
cmd.pipelineBarrier2({.memoryBarrierCount=1,.pMemoryBarriers=&b}, dispatch);
const vk::BufferImageCopy region{.imageSubresource={aspect,0,0,1},
.imageOffset={s32(w-1),s32(h-1),0},.imageExtent={1,1,1}};
cmd.copyImageToBuffer(image, layout, readback, region, dispatch);
const vk::MemoryBarrier2 host{.srcStageMask=vk::PipelineStageFlagBits2::eTransfer,
.srcAccessMask=vk::AccessFlagBits2::eTransferWrite,
.dstStageMask=vk::PipelineStageFlagBits2::eHost,
.dstAccessMask=vk::AccessFlagBits2::eHostRead};
cmd.pipelineBarrier2({.memoryBarrierCount=1,.pMemoryBarriers=&host}, dispatch);
scheduler.Finish();
u32 result;
std::memcpy(&result, ai.pMappedData, 4);
return result;
};
VideoCore::ImageViewInfo cv;
cv.format = ci.pixel_format;
auto dv = cv;
dv.format = di.pixel_format;
const auto clear_native = [&](u32 rgba, u32 stencil = 7) {
vk::ClearValue clear{};
clear.color.float32 = std::array{float(rgba & 255)/255, float((rgba>>8)&255)/255,
float((rgba>>16)&255)/255, 1.0f};
runtime.ClearImage(&images[color_id], {}, clear);
runtime.Transit(&images[depth_id], vk::ImageLayout::eTransferDstOptimal,
vk::PipelineStageFlagBits2::eTransfer, vk::AccessFlagBits2::eTransferWrite);
runtime.FlushBarriers();
scheduler.CommandBuffer().clearDepthStencilImage(
images[depth_id].GetImage(), vk::ImageLayout::eTransferDstOptimal,
vk::ClearDepthStencilValue{0.25f, stencil},
vk::ImageSubresourceRange{vk::ImageAspectFlagBits::eDepth |
vk::ImageAspectFlagBits::eStencil, 0, 1, 0, 1}, dispatch);
};
constexpr u32 stencil_values[] = {0, 255, 128, 85, 170, 7, 13, 255, 0, 170};
u32 stencil_index = 0;
for (int preset : {3,1,4,2,0,3,0,1,0,3}) {
const u32 copied_stencil = stencil_values[stencil_index++];
const u32 resolved_stencil = copied_stencil ^ 255;
const auto output = stencil_index > 7 ? SceneResolution::Size{3840,2160}
: SceneResolution::Size{};
const auto size = SceneResolution::ForPreset(preset, output);
targets.SetSize(size);
assert(SceneResolution::Unpack(SceneResolution::Pack(size)) == size);
clear_native(0xffff0000, copied_stencil); // blue
auto c = targets.Read(color_id,cv);
assert(pixel(c.image,vk::ImageAspectFlagBits::eColor,c.layout,size.width,size.height)==0xffff0000);
auto d = targets.Read(depth_id,dv);
const u32 depth_bits = pixel(d.image,vk::ImageAspectFlagBits::eDepth,d.layout,size.width,size.height);
assert(std::bit_cast<float>(depth_bits)==0.25f);
assert((pixel(d.image,vk::ImageAspectFlagBits::eStencil,d.layout,size.width,size.height)&255)==copied_stencil);
c = targets.Attachment(color_id,cv);
d = targets.Attachment(depth_id,dv);
auto cmd = scheduler.CommandBuffer();
const vk::RenderingAttachmentInfo color{.imageView=c.view,.imageLayout=c.layout,
.loadOp=vk::AttachmentLoadOp::eClear,.storeOp=vk::AttachmentStoreOp::eStore,
.clearValue=vk::ClearValue{.color={.float32=std::array{1.f,0.f,0.f,1.f}}}};
const vk::RenderingAttachmentInfo depth{.imageView=d.view,.imageLayout=d.layout,
.loadOp=vk::AttachmentLoadOp::eClear,.storeOp=vk::AttachmentStoreOp::eStore,
.clearValue=vk::ClearValue{.depthStencil={0.75f,resolved_stencil}}};
cmd.beginRendering({.renderArea={{0,0},{size.width,size.height}},.layerCount=1,
.colorAttachmentCount=1,.pColorAttachments=&color,
.pDepthAttachment=&depth,.pStencilAttachment=&depth}, dispatch);
cmd.endRendering(dispatch);
runtime.Transit(&images[color_id],vk::ImageLayout::eTransferSrcOptimal,
vk::PipelineStageFlagBits2::eTransfer,vk::AccessFlagBits2::eTransferRead);
runtime.FlushBarriers();
assert(pixel(images[color_id].GetImage(),vk::ImageAspectFlagBits::eColor,
vk::ImageLayout::eTransferSrcOptimal,1920,1080)==0xff0000ff);
runtime.Transit(&images[depth_id],vk::ImageLayout::eTransferSrcOptimal,
vk::PipelineStageFlagBits2::eTransfer,vk::AccessFlagBits2::eTransferRead);
runtime.FlushBarriers();
assert(std::bit_cast<float>(pixel(images[depth_id].GetImage(),vk::ImageAspectFlagBits::eDepth,
vk::ImageLayout::eTransferSrcOptimal,1920,1080))==0.75f);
assert((pixel(images[depth_id].GetImage(),vk::ImageAspectFlagBits::eStencil,
vk::ImageLayout::eTransferSrcOptimal,1920,1080)&255)==resolved_stencil);
clear_native(0xff00ff00); // overwrite after reduced render: proxy must be invalidated
c=targets.Read(color_id,cv);
assert(pixel(c.image,vk::ImageAspectFlagBits::eColor,c.layout,size.width,size.height)==0xff00ff00);
std::printf("Scene targets: %ux%u color/depth/stencil roundtrip PASS\n",size.width,size.height);
}
// Half-resolution mip chain (the game's bloom pyramid): one proxy per level, scaled by the
// scene's factor, resolved into its own native level.
auto hi = ci;
hi.size = {960, 540, 1};
hi.resources = {4, 1};
const auto half_id = images.insert(instance, runtime, views, hi);
const auto scene = SceneResolution::ForPreset(4); // 640x360 (x3 of 1080p)
targets.SetSize(scene);
auto& half = images[half_id];
assert(targets.Eligible(half) && !targets.EligibleScene(half));
assert((targets.ProxySize(half, 0) == SceneResolution::Size{320, 180}));
assert((targets.ProxySize(half, 2) == SceneResolution::Size{80, 45}));
auto lv = cv;
lv.range.base.level = 2;
const auto proxy = targets.Attachment(half_id, lv);
{
auto cmd = scheduler.CommandBuffer();
const vk::RenderingAttachmentInfo color{.imageView=proxy.view,.imageLayout=proxy.layout,
.loadOp=vk::AttachmentLoadOp::eClear,.storeOp=vk::AttachmentStoreOp::eStore,
.clearValue=vk::ClearValue{.color={.float32=std::array{0.f,1.f,0.f,1.f}}}};
cmd.beginRendering({.renderArea={{0,0},{80,45}},.layerCount=1,
.colorAttachmentCount=1,.pColorAttachments=&color}, dispatch);
cmd.endRendering(dispatch);
}
assert(targets.ProxyCurrent(half, 2, 1) && !targets.ProxyCurrent(half, 0, 4));
// A native access resolves the level: its last texel (239, 134) is the proxy's colour.
runtime.Transit(&half, vk::ImageLayout::eTransferSrcOptimal,
vk::PipelineStageFlagBits2::eTransfer, vk::AccessFlagBits2::eTransferRead);
runtime.FlushBarriers();
{
scheduler.EndRendering();
const auto cmd = scheduler.CommandBuffer();
const vk::MemoryBarrier2 b{.srcStageMask=vk::PipelineStageFlagBits2::eAllCommands,
.srcAccessMask=vk::AccessFlagBits2::eMemoryWrite,
.dstStageMask=vk::PipelineStageFlagBits2::eTransfer,
.dstAccessMask=vk::AccessFlagBits2::eTransferRead};
cmd.pipelineBarrier2({.memoryBarrierCount=1,.pMemoryBarriers=&b}, dispatch);
const vk::BufferImageCopy region{.imageSubresource={vk::ImageAspectFlagBits::eColor,2,0,1},
.imageOffset={239,134,0},.imageExtent={1,1,1}};
cmd.copyImageToBuffer(half.GetImage(), vk::ImageLayout::eTransferSrcOptimal, readback,
region, dispatch);
scheduler.Finish();
u32 result;
std::memcpy(&result, ai.pMappedData, 4);
assert(result == 0xff00ff00);
}
std::puts("Scene targets: half-resolution mip level proxy roundtrip PASS");
scheduler.Finish();
vmaDestroyBuffer(instance.GetAllocator(),readback,allocation);
}