feat(agc): add agc texture support definitions (wip)

This commit is contained in:
boykopovar 2026-09-23 07:25:23 +03:00
parent 77f44a1a8d
commit 5c7dc027ea
22 changed files with 464 additions and 2 deletions

View File

@ -1,6 +1,7 @@
#include "BdaAbi.hpp"
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Execution/include/BdaFeatures.hpp"
#include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp"
#include "prx/libc/include/General.hpp"
#include <SDL_loadso.h>
#include <SDL_error.h>
@ -63,6 +64,9 @@ struct VulkanDevice::State {
bool fragmentShaderBarycentric = false;
bool depthClipControl = false;
bool depthRangeUnrestricted = false;
bool samplerAnisotropy = false;
bool textureCompressionBC = false;
std::unique_ptr<Graphics::TextureDetiler> detiler;
VkPhysicalDeviceMeshShaderPropertiesEXT meshLimits{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_PROPERTIES_EXT};
template<typename TFunction>
@ -128,6 +132,7 @@ struct VulkanDevice::State {
if (device != VK_NULL_HANDLE) {
const auto idle = reinterpret_cast<PFN_vkDeviceWaitIdle>(deviceProc(device, "vkDeviceWaitIdle"))(device);
if (idle != VK_SUCCESS && idle != VK_ERROR_DEVICE_LOST) std::terminate();
detiler.reset();
if (presentQueued && idle != VK_ERROR_DEVICE_LOST) {
const auto result = reinterpret_cast<PFN_vkWaitForFences>(deviceProc(device, "vkWaitForFences"))(device, 1, &presentFence, VK_TRUE, std::numeric_limits<std::uint64_t>::max());
if (result != VK_SUCCESS && result != VK_ERROR_DEVICE_LOST) std::terminate();
@ -363,6 +368,11 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u
enabled.tessellationShader = available.tessellationShader;
state->tessellationShader = enabled.tessellationShader == VK_TRUE;
if (state->tessellationShader) state->capabilities.push_back(3);
require(available.samplerAnisotropy && available.textureCompressionBC, "device lacks sampler anisotropy or BC texture compression support required for texture sampling");
enabled.samplerAnisotropy = VK_TRUE;
enabled.textureCompressionBC = VK_TRUE;
state->samplerAnisotropy = true;
state->textureCompressionBC = true;
deviceInfo.pEnabledFeatures = &enabled;
deviceInfo.enabledExtensionCount = static_cast<std::uint32_t>(deviceExtensions.size());
deviceInfo.ppEnabledExtensionNames = deviceExtensions.data();

View File

@ -11,6 +11,8 @@
namespace AgcDriver::Graphics {
class TextureDetiler;
inline void Require(bool condition, const std::string& reason) {
if (!condition) throw std::runtime_error("AGC graphics: " + reason);
}
@ -37,6 +39,9 @@ struct Context {
bool bufferDeviceAddress = false;
VkPhysicalDeviceSubgroupProperties subgroup{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES};
bool fragmentShaderBarycentric = false;
bool samplerAnisotropy = false;
bool textureCompressionBC = false;
TextureDetiler* detiler = nullptr;
template<typename TFunction>
TFunction Function(const char* name) const {

View File

@ -0,0 +1,32 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_GUESTSAMPLERRESOURCE_HPP
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_GUESTSAMPLERRESOURCE_HPP
#ifndef VK_NO_PROTOTYPES
#define VK_NO_PROTOTYPES
#endif
#include <vulkan/vulkan.h>
#include <cstdint>
#include <span>
namespace AgcDriver::Graphics {
struct GuestSamplerResource {
VkFilter magFilter;
VkFilter minFilter;
VkSamplerMipmapMode mipmapMode;
VkSamplerAddressMode addressModeU;
VkSamplerAddressMode addressModeV;
VkSamplerAddressMode addressModeW;
bool anisotropyEnable;
float maxAnisotropy;
float minLod;
float maxLod;
float lodBias;
VkBorderColor borderColor;
};
GuestSamplerResource DecodeSamplerResource(std::span<const std::uint32_t> words);
}
#endif

View File

@ -0,0 +1,46 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_GUESTTEXTURERESOURCE_HPP
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_GUESTTEXTURERESOURCE_HPP
#include "Recompiler.hpp"
#include <cstdint>
#include <span>
namespace AgcDriver::Graphics {
enum class TextureTileMode {
kLinear,
kStandard256B,
kStandard4KB,
kStandard64KB
};
enum class TextureDimension {
k1D,
k2D,
k2DArray,
kCube
};
struct GuestTextureResource {
std::uint64_t baseAddress;
std::uint32_t width;
std::uint32_t height;
std::uint32_t depthOrLastArray;
std::uint32_t baseArray;
std::uint32_t mipCount;
std::uint32_t baseLevel;
TextureTileMode tileMode;
TextureDimension dimension;
std::uint32_t format;
std::uint8_t dstSelX;
std::uint8_t dstSelY;
std::uint8_t dstSelZ;
std::uint8_t dstSelW;
};
GuestTextureResource DecodeTextureResource(std::span<const std::uint32_t> words);
bool MatchesGuestDimension(ShaderRecompiler::DescriptorImageShape shape, TextureDimension dimension);
}
#endif

View File

@ -0,0 +1,27 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_SAMPLER_HPP
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_SAMPLER_HPP
#include "prx/libSceAgcDriver/Graphics/include/Context.hpp"
#include "prx/libSceAgcDriver/Graphics/include/GuestSamplerResource.hpp"
namespace AgcDriver::Graphics {
class Sampler {
public:
Sampler(const Context& context, const GuestSamplerResource& descriptor);
~Sampler();
Sampler(const Sampler&) = delete;
Sampler& operator=(const Sampler&) = delete;
VkSampler Handle() const;
private:
void release() noexcept;
const Context& context;
VkSampler sampler = VK_NULL_HANDLE;
};
}
#endif

View File

@ -3,6 +3,8 @@
#include "prx/libSceAgcDriver/Graphics/include/Resources.hpp"
#include "prx/libSceAgcDriver/Graphics/include/BdaResources.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Sampler.hpp"
#include "Recompiler.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Shaders.hpp"
#include <array>
@ -35,6 +37,7 @@ private:
void build(std::span<const CompiledShader> shaders, const ColorTarget* target, std::uint64_t indexAddress, std::size_t indexBytes);
std::size_t addGuestBuffer(std::span<const std::uint32_t> words, const ColorTarget* target, std::uint64_t indexAddress, std::size_t indexBytes);
std::size_t addDataBuffer(std::span<const std::uint32_t> words);
void addImageBinding(const ShaderRecompiler::DescriptorBinding& binding, VkShaderStageFlags flags);
void release() noexcept;
void prepareAddressBindings(std::span<const CompiledShader> shaders, std::span<const GuestMemorySnapshot> snapshots);
VkDescriptorBufferInfo descriptor(const Allocation& allocation) const;
@ -46,6 +49,8 @@ private:
VkDescriptorSet _set = VK_NULL_HANDLE;
VkDescriptorPool pool = VK_NULL_HANDLE;
std::vector<Allocation> allocations;
std::vector<std::unique_ptr<Texture>> textures;
std::vector<std::unique_ptr<Sampler>> samplers;
};
}

View File

@ -0,0 +1,30 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_TEXTURE_HPP
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_TEXTURE_HPP
#include "prx/libSceAgcDriver/Graphics/include/Context.hpp"
#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp"
#include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp"
namespace AgcDriver::Graphics {
class Texture {
public:
Texture(const Context& context, TextureDetiler& detiler, const GuestTextureResource& descriptor, VkComponentMapping components);
~Texture();
Texture(const Texture&) = delete;
Texture& operator=(const Texture&) = delete;
VkImageView View() const;
private:
void release() noexcept;
const Context& context;
VkImage image = VK_NULL_HANDLE;
VkDeviceMemory memory = VK_NULL_HANDLE;
VkImageView view = VK_NULL_HANDLE;
};
}
#endif

View File

@ -0,0 +1,35 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_TEXTUREDETILER_HPP
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_TEXTUREDETILER_HPP
#include "prx/libSceAgcDriver/Graphics/include/Context.hpp"
#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp"
#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp"
#include <cstdint>
#include <utility>
#include <vector>
namespace AgcDriver::Graphics {
class TextureDetiler {
public:
explicit TextureDetiler(const Context& context);
~TextureDetiler();
TextureDetiler(const TextureDetiler&) = delete;
TextureDetiler& operator=(const TextureDetiler&) = delete;
void Dispatch(VkCommandBuffer commands, TextureTileMode tileMode, std::uint32_t elementBytes, VkBuffer source, std::uint64_t sourceOffset, VkBuffer destination, std::uint64_t destinationOffset, const TileMipLayout& layout);
private:
VkPipeline pipeline(TextureTileMode tileMode, std::uint32_t elementBytes);
void release() noexcept;
const Context& context;
VkDescriptorSetLayout descriptorLayout = VK_NULL_HANDLE;
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
VkShaderModule module = VK_NULL_HANDLE;
std::vector<std::pair<std::uint32_t, VkPipeline>> pipelines;
};
}
#endif

View File

@ -0,0 +1,20 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_TEXTUREFORMAT_HPP
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_TEXTUREFORMAT_HPP
#ifndef VK_NO_PROTOTYPES
#define VK_NO_PROTOTYPES
#endif
#include <vulkan/vulkan.h>
#include <cstdint>
namespace AgcDriver::Graphics {
VkFormat ResolveTextureFormat(std::uint32_t guestFormat);
std::uint32_t BytesPerElement(std::uint32_t guestFormat);
bool IsBlockCompressed(std::uint32_t guestFormat);
std::uint32_t BlockWidth(std::uint32_t guestFormat);
std::uint32_t BlockHeight(std::uint32_t guestFormat);
}
#endif

View File

@ -0,0 +1,29 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_TEXTURETILING_HPP
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_TEXTURETILING_HPP
#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp"
#include <cstdint>
#include <vector>
namespace AgcDriver::Graphics {
struct TileMipLayout {
std::uint64_t tiledOffset;
std::uint64_t tiledSize;
std::uint64_t linearOffset;
std::uint64_t linearSize;
std::uint32_t width;
std::uint32_t height;
std::uint32_t blocksPerRow;
std::uint32_t pitchBytes;
bool tail;
std::uint32_t tailX;
std::uint32_t tailY;
};
std::vector<TileMipLayout> ComputeMipLayout(TextureTileMode tileMode, std::uint32_t format, std::uint32_t width, std::uint32_t height, std::uint32_t mipCount);
std::uint64_t ComputeSurfaceSize(const std::vector<TileMipLayout>& mips, std::uint32_t arrayLayers);
}
#endif

View File

@ -0,0 +1,26 @@
#version 450
layout(constant_id = 0) const uint ELEMENT_BYTES = 4u;
layout(constant_id = 1) const uint BLOCK_BYTES = 256u;
layout(constant_id = 2) const uint TILE_FAMILY = 0u;
layout(local_size_x = 8, local_size_y = 8) in;
layout(set = 0, binding = 0, std430) readonly buffer Input { uint data[]; } inputBuffer;
layout(set = 0, binding = 1, std430) buffer Output { uint data[]; } outputBuffer;
layout(push_constant) uniform Push {
uint srcBase;
uint dstBase;
uint width;
uint height;
uint pitchBytes;
uint blocksPerRow;
uint tail;
uint tailX;
uint tailY;
uint elementBytes;
} params;
void main() {
}

View File

@ -0,0 +1,12 @@
#include "prx/libSceAgcDriver/Graphics/include/GuestSamplerResource.hpp"
#include <stdexcept>
#include <string>
namespace AgcDriver::Graphics {
GuestSamplerResource DecodeSamplerResource(std::span<const std::uint32_t> words) {
static_cast<void>(words);
throw std::runtime_error(std::string(__func__) + " not implemented");
}
}

View File

@ -0,0 +1,18 @@
#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp"
#include <stdexcept>
#include <string>
namespace AgcDriver::Graphics {
GuestTextureResource DecodeTextureResource(std::span<const std::uint32_t> words) {
static_cast<void>(words);
throw std::runtime_error(std::string(__func__) + " not implemented");
}
bool MatchesGuestDimension(ShaderRecompiler::DescriptorImageShape shape, TextureDimension dimension) {
static_cast<void>(shape);
static_cast<void>(dimension);
throw std::runtime_error(std::string(__func__) + " not implemented");
}
}

View File

@ -0,0 +1,24 @@
#include "prx/libSceAgcDriver/Graphics/include/Sampler.hpp"
#include <stdexcept>
#include <string>
namespace AgcDriver::Graphics {
Sampler::Sampler(const Context& context, const GuestSamplerResource& descriptor) : context(context) {
static_cast<void>(descriptor);
throw std::runtime_error(std::string(__func__) + " not implemented");
}
Sampler::~Sampler() {
release();
}
void Sampler::release() noexcept {
if (sampler) context.Function<PFN_vkDestroySampler>("vkDestroySampler")(context.device, sampler, nullptr);
}
VkSampler Sampler::Handle() const {
return sampler;
}
}

View File

@ -74,7 +74,10 @@ void ShaderResources::build(std::span<const CompiledShader> shaders, const Color
const bool addressRole = binding.role == ShaderRecompiler::DescriptorRole::BdaPagetable || binding.role == ShaderRecompiler::DescriptorRole::FaultBuffer;
const bool bufferRole = addressRole || binding.role == ShaderRecompiler::DescriptorRole::GuestBuffers || binding.role == ShaderRecompiler::DescriptorRole::ShaderData || binding.role == ShaderRecompiler::DescriptorRole::FlattenedSrt;
const bool imageRole = binding.role == ShaderRecompiler::DescriptorRole::GuestImages || binding.role == ShaderRecompiler::DescriptorRole::GuestSamplers;
Require(!imageRole, "sampled and storage image resources are not implemented");
if (imageRole) {
addImageBinding(binding, flags);
continue;
}
Require(bufferRole, std::string("unsupported descriptor role ") + roleName(binding.role));
Require(binding.kind == ShaderRecompiler::DescriptorKind::StorageBuffer, std::string("unsupported descriptor kind ") + kindName(binding.kind) + " for role " + roleName(binding.role) + ": only StorageBuffer is supported");
Require(!binding.readOnly, "read-only descriptors are unsupported because the recompiler emits no NonWritable decoration");
@ -161,6 +164,12 @@ std::size_t ShaderResources::addDataBuffer(std::span<const std::uint32_t> words)
return allocations.size() - 1;
}
void ShaderResources::addImageBinding(const ShaderRecompiler::DescriptorBinding& binding, VkShaderStageFlags flags) {
static_cast<void>(binding);
static_cast<void>(flags);
throw std::runtime_error(std::string(__func__) + " not implemented");
}
ShaderResources::~ShaderResources() {
release();
}

View File

@ -395,7 +395,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
const auto binding = std::find_if(shader.bindings.begin(), shader.bindings.end(), [&](const auto& item) { return item.descriptorSet == key.first && item.binding == key.second; });
Require(binding != shader.bindings.end(), "SPIR-V resource is absent from recompiler binding metadata");
Require(binding->kind == ShaderRecompiler::DescriptorKind::Sampler || binding->kind == ShaderRecompiler::DescriptorKind::SampledImage || binding->kind == ShaderRecompiler::DescriptorKind::StorageImage, "SPIR-V descriptor type disagrees with recompiler binding metadata");
Require(false, "sampled and storage image resources are not implemented");
Require(binding->kind != ShaderRecompiler::DescriptorKind::StorageImage, "storage image resources are not implemented");
} else {
Require(variable.storage == spv::StorageClassStorageBuffer && decoration.set && decoration.binding, "unsupported or unbound shader resource");
const auto key = std::make_pair(*decoration.set, *decoration.binding);

View File

@ -0,0 +1,28 @@
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
#include <stdexcept>
#include <string>
namespace AgcDriver::Graphics {
Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTextureResource& descriptor, VkComponentMapping components) : context(context) {
static_cast<void>(detiler);
static_cast<void>(descriptor);
static_cast<void>(components);
throw std::runtime_error(std::string(__func__) + " not implemented");
}
Texture::~Texture() {
release();
}
void Texture::release() noexcept {
if (view) context.Function<PFN_vkDestroyImageView>("vkDestroyImageView")(context.device, view, nullptr);
if (image) context.Function<PFN_vkDestroyImage>("vkDestroyImage")(context.device, image, nullptr);
if (memory) context.Function<PFN_vkFreeMemory>("vkFreeMemory")(context.device, memory, nullptr);
}
VkImageView Texture::View() const {
return view;
}
}

View File

@ -0,0 +1,36 @@
#include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp"
#include <stdexcept>
#include <string>
namespace AgcDriver::Graphics {
TextureDetiler::TextureDetiler(const Context& context) : context(context) {
throw std::runtime_error(std::string(__func__) + " not implemented");
}
TextureDetiler::~TextureDetiler() {
release();
}
void TextureDetiler::release() noexcept {
}
VkPipeline TextureDetiler::pipeline(TextureTileMode tileMode, std::uint32_t elementBytes) {
static_cast<void>(tileMode);
static_cast<void>(elementBytes);
throw std::runtime_error(std::string(__func__) + " not implemented");
}
void TextureDetiler::Dispatch(VkCommandBuffer commands, TextureTileMode tileMode, std::uint32_t elementBytes, VkBuffer source, std::uint64_t sourceOffset, VkBuffer destination, std::uint64_t destinationOffset, const TileMipLayout& layout) {
static_cast<void>(commands);
static_cast<void>(tileMode);
static_cast<void>(elementBytes);
static_cast<void>(source);
static_cast<void>(sourceOffset);
static_cast<void>(destination);
static_cast<void>(destinationOffset);
static_cast<void>(layout);
throw std::runtime_error(std::string(__func__) + " not implemented");
}
}

View File

@ -0,0 +1,32 @@
#include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp"
#include <stdexcept>
#include <string>
namespace AgcDriver::Graphics {
VkFormat ResolveTextureFormat(std::uint32_t guestFormat) {
static_cast<void>(guestFormat);
throw std::runtime_error(std::string(__func__) + " not implemented");
}
std::uint32_t BytesPerElement(std::uint32_t guestFormat) {
static_cast<void>(guestFormat);
throw std::runtime_error(std::string(__func__) + " not implemented");
}
bool IsBlockCompressed(std::uint32_t guestFormat) {
static_cast<void>(guestFormat);
throw std::runtime_error(std::string(__func__) + " not implemented");
}
std::uint32_t BlockWidth(std::uint32_t guestFormat) {
static_cast<void>(guestFormat);
throw std::runtime_error(std::string(__func__) + " not implemented");
}
std::uint32_t BlockHeight(std::uint32_t guestFormat) {
static_cast<void>(guestFormat);
throw std::runtime_error(std::string(__func__) + " not implemented");
}
}

View File

@ -0,0 +1,22 @@
#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp"
#include <stdexcept>
#include <string>
namespace AgcDriver::Graphics {
std::vector<TileMipLayout> ComputeMipLayout(TextureTileMode tileMode, std::uint32_t format, std::uint32_t width, std::uint32_t height, std::uint32_t mipCount) {
static_cast<void>(tileMode);
static_cast<void>(format);
static_cast<void>(width);
static_cast<void>(height);
static_cast<void>(mipCount);
throw std::runtime_error(std::string(__func__) + " not implemented");
}
std::uint64_t ComputeSurfaceSize(const std::vector<TileMipLayout>& mips, std::uint32_t arrayLayers) {
static_cast<void>(mips);
static_cast<void>(arrayLayers);
throw std::runtime_error(std::string(__func__) + " not implemented");
}
}

View File

@ -54,6 +54,12 @@ DescriptorRole RoleFor(DescriptorBindingKind kind) {
fail("DescriptorBindingBuilder::Populate binding kind has no descriptor role");
}
DescriptorImageShape ImageShapeFor(const std::vector<ImageResource>& images, const std::vector<std::uint32_t>& resources) {
static_cast<void>(images);
static_cast<void>(resources);
throw std::runtime_error(std::string(__func__) + " not implemented");
}
std::vector<std::uint32_t> GuestBuffersDescriptor(const std::vector<std::uint32_t>& resources, const ResourceSnapshot& snapshot) {
std::vector<std::uint32_t> result;
result.reserve(resources.size() * 4u);
@ -156,6 +162,7 @@ void DescriptorBindingBuilder::Populate(BindingAllocationResult& allocation, con
break;
case DescriptorRole::GuestImages:
physical.guestDescriptor = GuestImagesDescriptor(logical.resources, snapshot);
physical.imageShape = ImageShapeFor(program.Info().images, logical.resources);
break;
case DescriptorRole::GuestSamplers:
physical.guestDescriptor = GuestSamplersDescriptor(logical.resources, snapshot);

View File

@ -210,6 +210,14 @@ enum class DescriptorKind {
Sampler
};
enum class DescriptorImageShape {
Image1D,
Image2D,
Image2DArray,
ImageCube,
Image3D
};
enum class DescriptorRole {
GuestBuffers,
GuestImages,
@ -229,6 +237,7 @@ struct DescriptorBinding {
std::uint32_t count;
std::vector<std::uint32_t> guestDescriptor;
bool readOnly = false;
std::optional<DescriptorImageShape> imageShape;
};
struct VertexAttribute {