Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ struct SurfaceGeometry {
SurfaceGeometry DescribeSurface(const GuestTextureResource& descriptor);
bool LevelsFitAllocation(const GuestTextureResource& surface, std::uint32_t levels);
GuestTextureResource ViewedArrayWindow(const GuestTextureResource& viewed);
GuestTextureResource ArrayLayerWindow(const GuestTextureResource& viewed, std::uint32_t maxArrayLayers);

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -658,7 +658,9 @@ void reportGuestAllocation(const GuestTextureResource& resource, std::uint64_t g
}

// `guestBytes` is the surface size when the caller described the surface already (0: described here).
std::shared_ptr<Texture> cachedTexture(const Context& context, std::span<const std::uint32_t> words, const GuestTextureResource& resource, VkComponentMapping components, std::uint64_t guestBytes = 0, bool depthCompare = false, VkImageViewType multisampleViewType = VK_IMAGE_VIEW_TYPE_2D) {
std::shared_ptr<Texture> cachedTexture(const Context& context, std::span<const std::uint32_t> words, const GuestTextureResource& requested, VkComponentMapping components, std::uint64_t guestBytes = 0, bool depthCompare = false, VkImageViewType multisampleViewType = VK_IMAGE_VIEW_TYPE_2D) {
const auto resource = ArrayLayerWindow(requested, context.limits.maxImageArrayLayers);
if (resource.baseAddress != requested.baseAddress) guestBytes = 0;
if (resource.dimension == TextureDimension::k2DMsaa) {
Require(!depthCompare, "native multisampled depth comparison sampling is not implemented");
if (auto depth = DepthSurfaceTexture(context, words, resource, components, multisampleViewType)) {
Expand Down Expand Up @@ -1095,7 +1097,9 @@ static WrittenStorage WrittenStorageBytes(const GuestTextureResource& viewed, st
}

// `guestBytes` is the surface size when the caller described the surface already (0: described here).
std::shared_ptr<StorageTexture> cachedStorageTexture(const Context& context, std::span<const std::uint32_t> words, const GuestTextureResource& viewed, std::uint32_t mip, std::uint64_t guestBytes, bool written) {
std::shared_ptr<StorageTexture> cachedStorageTexture(const Context& context, std::span<const std::uint32_t> words, const GuestTextureResource& requested, std::uint32_t mip, std::uint64_t guestBytes, bool written) {
const auto viewed = ArrayLayerWindow(requested, context.limits.maxImageArrayLayers);
if (viewed.baseAddress != requested.baseAddress) guestBytes = 0;
Require(viewed.dimension != TextureDimension::k2DMsaa, "storage image access to a multisampled texture is not implemented");
RequireNoMultisampleTarget(viewed.baseAddress, guestBytes != 0 ? static_cast<std::size_t>(guestBytes) : 1u, "a storage image access");
if (auto depth = DepthSurfaceStorage(context, words, viewed, mip)) return depth;
Expand Down
2 changes: 2 additions & 0 deletions core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTex
imageInfo.format = vkFormat;
imageInfo.extent = {descriptor.width, descriptor.height, geometry.imageDepth};
imageInfo.mipLevels = descriptor.mipCount;
Require(geometry.imageLayers <= context.limits.maxImageArrayLayers, "a texture array with more layers than maxImageArrayLayers is not implemented");
imageInfo.arrayLayers = geometry.imageLayers;
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
Expand Down Expand Up @@ -1390,6 +1391,7 @@ StorageTexture::StorageTexture(const Context& context, TextureDetiler& detiler,
imageInfo.format = vkFormat;
imageInfo.extent = {descriptor.width, descriptor.height, geometry.imageDepth};
imageInfo.mipLevels = descriptor.mipCount;
Require(geometry.imageLayers <= context.limits.maxImageArrayLayers, "a texture array with more layers than maxImageArrayLayers is not implemented");
imageInfo.arrayLayers = geometry.imageLayers;
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
Expand Down
19 changes: 19 additions & 0 deletions core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,25 @@ GuestTextureResource ViewedArrayWindow(const GuestTextureResource& viewed) {
return window;
}

GuestTextureResource ArrayLayerWindow(const GuestTextureResource& viewed, std::uint32_t maxArrayLayers) {
const bool layered = viewed.dimension == TextureDimension::k2DArray || viewed.dimension == TextureDimension::k1DArray || viewed.dimension == TextureDimension::kCube;
if (viewed.depthOrLastArray < maxArrayLayers || !layered) return viewed;
Require(viewed.dimension != TextureDimension::kCube, "an image with more layers than maxImageArrayLayers that is not a 1D or 2D array is not implemented");
Require(viewed.baseArray <= viewed.depthOrLastArray, "an image view's first array slice lies past its last");
Require(viewed.depthOrLastArray - viewed.baseArray < maxArrayLayers, "an image view of more array slices than maxImageArrayLayers is not implemented");
if (const auto xorMode = XorSwizzleMode(viewed.tileMode); xorMode != 0u) {
const auto* equation = FindTextureSwizzleEquation(xorMode, BytesPerElement(viewed.format));
Require(equation != nullptr && std::none_of(equation->bits.begin(), equation->bits.end(), [](std::uint32_t bit) { return (bit & 0xff000000u) != 0; }), "an image view past maxImageArrayLayers of a swizzle that depends on the slice is not implemented");
}
const auto geometry = DescribeSurface(viewed);
Require(!geometry.thick && geometry.layers == viewed.depthOrLastArray + 1u, "an array past maxImageArrayLayers must be a thin array");
auto window = viewed;
window.baseAddress = viewed.baseAddress + geometry.GuestLayerOffset(viewed.baseArray);
window.depthOrLastArray = viewed.depthOrLastArray - viewed.baseArray;
window.baseArray = 0;
return window;
}

std::vector<TileMipLayout> ComputeElementMipLayout(TextureTileMode tileMode, std::uint32_t bytesPerElement, std::uint32_t width, std::uint32_t height, std::uint32_t mipCount) {
Require(width != 0 && height != 0 && mipCount != 0 && mipCount <= 16u, "invalid surface mip chain");
if (tileMode == TextureTileMode::kLinear) return ComputeLinearMipLayout(bytesPerElement, 1u, 1u, width, height, mipCount);
Expand Down
1 change: 1 addition & 0 deletions core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4810,6 +4810,7 @@ AgcDriver::Graphics::Context mockContext() {
context.limits.maxPerStageDescriptorStorageBuffers = 16;
context.limits.maxPerStageResources = 128;
context.limits.maxDescriptorSetStorageBuffers = 32;
context.limits.maxImageArrayLayers = 2048;
return context;
}

Expand Down
43 changes: 43 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -440,4 +440,47 @@ void RunTextureTilingTests() {
volume.baseArray = 0;
Require(ViewedArrayWindow(volume).baseAddress == cube.baseAddress, "a volume keeps its descriptor");
}

{
GuestTextureResource array{};
array.baseAddress = 0x10000000ull;
array.width = 16;
array.height = 16;
array.depthOrLastArray = 3200;
array.baseArray = 1536;
array.mipCount = 2;
array.tileMode = TextureTileMode::kStandard64KB;
array.dimension = TextureDimension::k2DArray;
array.format = 56;
const auto geometry = DescribeSurface(array);
const auto window = ArrayLayerWindow(array, 2048);
Require(window.baseAddress == array.baseAddress + 1536ull * geometry.layerBytes, "a storage view past maxImageArrayLayers must start at its first viewed slice");
Require(window.baseArray == 0 && window.depthOrLastArray == 1664, "a storage view past maxImageArrayLayers must hold exactly its viewed slices");
Require(DescribeSurface(window).layerBytes == geometry.layerBytes && DescribeSurface(window).mips[1].tiledOffset == geometry.mips[1].tiledOffset, "a viewed slice must keep the layout of the whole array's slice");

auto fitting = array;
fitting.depthOrLastArray = 2047;
const auto kept = ArrayLayerWindow(fitting, 2048);
Require(kept.baseAddress == fitting.baseAddress && kept.baseArray == fitting.baseArray && kept.depthOrLastArray == fitting.depthOrLastArray, "an array within maxImageArrayLayers must keep its descriptor");

auto linear = array;
linear.tileMode = TextureTileMode::kLinear;
Require(ArrayLayerWindow(linear, 2048).baseAddress == linear.baseAddress + 1536ull * DescribeSurface(linear).layerBytes, "a linear storage view past maxImageArrayLayers must start at its first viewed slice");

auto wide = array;
wide.baseArray = 1000;
reject([&] { static_cast<void>(ArrayLayerWindow(wide, 2048)); }, "more array slices than maxImageArrayLayers");
auto xorSwizzle = array;
xorSwizzle.tileMode = TextureTileMode::kR64KBX;
reject([&] { static_cast<void>(ArrayLayerWindow(xorSwizzle, 2048)); }, "depends on the slice");
auto volume = array;
volume.dimension = TextureDimension::k3D;
Require(ArrayLayerWindow(volume, 2048).baseAddress == volume.baseAddress && ArrayLayerWindow(volume, 2048).depthOrLastArray == volume.depthOrLastArray, "a volume deeper than maxImageArrayLayers is not an array and keeps its descriptor");
auto flat = array;
flat.dimension = TextureDimension::k2D;
Require(ArrayLayerWindow(flat, 2048).baseAddress == flat.baseAddress && ArrayLayerWindow(flat, 2048).depthOrLastArray == flat.depthOrLastArray, "a 2D image is one layer whatever its last array field holds and keeps its descriptor");
auto cube = array;
cube.dimension = TextureDimension::kCube;
reject([&] { static_cast<void>(ArrayLayerWindow(cube, 2048)); }, "not a 1D or 2D array");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp"
#include "Recompiler.hpp"
#include "VulkanTestDevice.hpp"
#include <algorithm>
#include <array>
#include <bit>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <iostream>
#include <span>
#include <string>
#include <vector>

namespace {

using AgcDriver::Graphics::Require;
using ShaderRecompiler::ShaderStage;

constexpr std::uint32_t Threads = 32;
constexpr std::uint32_t Width = 8;
constexpr std::uint32_t Layers = 3;
constexpr std::uint32_t BaseArray = 8100;
constexpr std::uint32_t LastArray = BaseArray + Layers - 1u;
constexpr std::uint32_t Format8888UNorm = 56;
constexpr std::uint32_t Type1DArray = 12;
constexpr std::uint32_t ClampEdge = 2;

alignas(256) std::array<std::uint32_t, Threads * 4> Input{};
alignas(256) std::array<float, Threads * 8> Output{};
alignas(65536) std::array<std::uint8_t, 4u << 20u> Texels{};

alignas(256) constexpr std::array<std::uint32_t, 15> Code{
0x34020084, 0xe0381000, 0x80000201, 0xbf8c3f70, 0xf09c0f20, 0x00820602, 0xf0001f20, 0x00020a04,
0x34020085, 0xbf8c3f70, 0xe0781000, 0x80010601, 0xe0781010, 0x80010a01, 0xbf810000,
};

std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t bytes) {
const auto address = reinterpret_cast<std::uintptr_t>(data);
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu), bytes, 0x31016facu};
}

std::array<std::uint32_t, 8> TextureDescriptor(std::uint32_t baseArray = BaseArray) {
const auto address = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(Texels.data()));
return {
static_cast<std::uint32_t>(address >> 8u),
static_cast<std::uint32_t>((address >> 40u) & 0xffu) | (Format8888UNorm << 20u) | (((Width - 1u) & 3u) << 30u),
(Width - 1u) >> 2u,
0xfacu | (Type1DArray << 28u),
LastArray | (baseArray << 16u), 0u, 0u, 0u,
};
}

std::array<std::uint32_t, 4> SamplerDescriptor() {
return {ClampEdge | (ClampEdge << 3u) | (ClampEdge << 6u), 0u, 0u, 0u};
}

std::array<std::uint8_t, 4> Texel(std::uint32_t x, std::uint32_t guestLayer) {
return {static_cast<std::uint8_t>((guestLayer * Width + x) * 9u + 3u), static_cast<std::uint8_t>(x * 30u + guestLayer % 7u), static_cast<std::uint8_t>(guestLayer), 0xff};
}

void FillTexels() {
Texels.fill(0);
const auto surface = AgcDriver::Graphics::DescribeSurface(AgcDriver::Graphics::DecodeTextureResource(TextureDescriptor()));
const auto& mip = surface.mips.at(0);
Require(surface.layers == LastArray + 1u && surface.guestBytes <= Texels.size(), "the test array does not fit its storage");
for (std::uint32_t layer = 0; layer <= LastArray; ++layer) {
for (std::uint32_t x = 0; x < Width; ++x) {
const auto texel = Texel(x, layer);
std::copy(texel.begin(), texel.end(), Texels.begin() + static_cast<std::ptrdiff_t>(surface.GuestLayerOffset(layer) + mip.tiledOffset + x * 4u));
}
}
}

void FillInput() {
constexpr std::array<float, 8> layers{0.0f, 1.0f, 2.0f, 0.4f, 1.6f, 2.4f, -1.0f, 5.0f};
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
Input[tid * 4u + 0u] = std::bit_cast<std::uint32_t>(-0.2f + 0.045f * static_cast<float>(tid));
Input[tid * 4u + 1u] = std::bit_cast<std::uint32_t>(layers[tid % layers.size()]);
Input[tid * 4u + 2u] = tid % Width;
Input[tid * 4u + 3u] = (tid / Width) % Layers;
}
}

void Expect(std::uint32_t tid, const char* name, const float* actual, const std::array<std::uint8_t, 4>& expected) {
for (std::uint32_t component = 0; component < 4u; ++component) {
const float scaled = actual[component] * 255.0f;
Require(std::fabs(scaled - std::round(scaled)) < 1e-3f && std::lround(scaled) == expected[component], std::string(name) + ": thread " + std::to_string(tid) + " component " + std::to_string(component) + " is " + std::to_string(scaled) + "/255, expected " + std::to_string(expected[component]) + "/255");
}
}

void Run(AgcDriver::VulkanDevice& device, std::uint32_t baseArray = BaseArray) {
Output.fill(-1.0f);
std::vector<std::uint32_t> userData(20, 0u);
const auto input = BufferDescriptor(Input.data(), static_cast<std::uint32_t>(sizeof(Input)));
const auto output = BufferDescriptor(Output.data(), static_cast<std::uint32_t>(sizeof(Output)));
const auto texture = TextureDescriptor(baseArray);
const auto sampler = SamplerDescriptor();
std::copy(input.begin(), input.end(), userData.begin());
std::copy(output.begin(), output.end(), userData.begin() + 4);
std::copy(texture.begin(), texture.end(), userData.begin() + 8);
std::copy(sampler.begin(), sampler.end(), userData.begin() + 16);
const std::span<const std::uint32_t> code(Code);
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1};
ShaderRecompiler::RecompileRequest request{
{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
{32, 0, userData, compute, std::nullopt, std::nullopt, memory},
device.Target(),
{0, 0, 0, 128}
};
request.useCache = false;
const auto result = ShaderRecompiler::Recompile(request);
device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
device.WaitIdle();
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
const float u = std::bit_cast<float>(Input[tid * 4u + 0u]);
const float layer = std::clamp(std::nearbyint(std::bit_cast<float>(Input[tid * 4u + 1u])), 0.0f, static_cast<float>(Layers - 1u));
const auto x = static_cast<std::uint32_t>(std::clamp(static_cast<int>(std::floor(u * static_cast<float>(Width))), 0, static_cast<int>(Width) - 1));
Expect(tid, "image_sample_lz on a window of a 1D array past maxImageArrayLayers", &Output[tid * 8u], Texel(x, baseArray + static_cast<std::uint32_t>(layer)));
Expect(tid, "image_load on a window of a 1D array past maxImageArrayLayers", &Output[tid * 8u + 4u], Texel(Input[tid * 4u + 2u], baseArray + Input[tid * 4u + 3u]));
}
}

}

int main() {
try {
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
FillTexels();
FillInput();
Run(*device);
try {
Run(*device, 0);
} catch (const std::exception& error) {
if (std::string(error.what()).find("an image view of more array slices than maxImageArrayLayers") == std::string::npos) throw;
}
std::puts("a window of a 1D array past maxImageArrayLayers was sampled and loaded from its viewed layers");
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}
Loading
Loading