Skip to content
Closed
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 @@ -61,11 +61,14 @@ struct GuestTextureResource {
std::uint64_t dccAddress = 0;
bool dccAlphaOnMsb = false;
std::uint32_t minLod = 0;
bool emptyView = false;
std::uint32_t emptyBaseLevel = 0;
};

float EffectiveMinLod(const GuestTextureResource& resource);

GuestTextureResource DecodeTextureResource(std::span<const std::uint32_t> words);
void RequireLevelsPresent(const GuestTextureResource& resource);
bool MatchesGuestDimension(ShaderRecompiler::DescriptorImageShape shape, TextureDimension dimension);

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ GuestTextureResource DecodeTextureResource(std::span<const std::uint32_t> words)
} catch (const std::exception& error) {
throw std::runtime_error(std::string(error.what()) + describe());
}
Require(baseLevel <= maxMip, "guest texture descriptor starts past the surface's last mip level" + describe());
const bool emptyView = baseLevel > maxMip;
// Views may name levels past MAX_MIP (a 512x512 view through 1x1 over a 9-level surface); the
// hardware never addresses them, so the view ends at the surface's last level.
lastLevel = std::min(lastLevel, maxMip);
Expand Down Expand Up @@ -164,7 +164,9 @@ GuestTextureResource DecodeTextureResource(std::span<const std::uint32_t> words)
result.depthOrLastArray = depth;
result.baseArray = baseArray;
result.mipCount = maxMip + 1u;
result.baseLevel = baseLevel;
result.baseLevel = emptyView ? maxMip : baseLevel;
result.emptyView = emptyView;
result.emptyBaseLevel = emptyView ? baseLevel : 0u;
result.lastLevel = lastLevel;
result.tileMode = tileMode;
result.dimension = dimension;
Expand All @@ -184,6 +186,11 @@ float EffectiveMinLod(const GuestTextureResource& resource) {
return std::min(static_cast<float>(resource.minLod) / 256.0f, static_cast<float>(resource.lastLevel));
}

void RequireLevelsPresent(const GuestTextureResource& resource) {
if (!resource.emptyView) return;
throw std::runtime_error("AGC graphics: guest texture descriptor starts past the surface's last mip level (format " + std::to_string(resource.format) + ", " + std::to_string(resource.width) + "x" + std::to_string(resource.height) + ", mip " + std::to_string(resource.emptyBaseLevel) + " of " + std::to_string(resource.mipCount) + ")");
}

bool MatchesGuestDimension(ShaderRecompiler::DescriptorImageShape shape, TextureDimension dimension) {
switch (shape) {
case ShaderRecompiler::DescriptorImageShape::Image1D: return dimension == TextureDimension::k1D;
Expand Down
16 changes: 15 additions & 1 deletion core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,14 @@ std::array<std::uint32_t, 8> SurfaceKey(const Context& context, const GuestTextu
}

// `guestBytes` is the surface size when the caller described the surface already (0: described here).
void NoteDroppedStores(const GuestTextureResource& resource) {
static std::mutex mutex;
static std::set<std::pair<std::uint64_t, std::uint32_t>> reported;
std::lock_guard lock(mutex);
if (!reported.insert({resource.baseAddress, resource.emptyBaseLevel}).second) return;
std::fprintf(stderr, "[gpu] storage image view of surface 0x%llx starts at mip %u, past its last mip %u: the shader's stores to it are dropped\n", static_cast<unsigned long long>(resource.baseAddress), resource.emptyBaseLevel, resource.mipCount - 1u);
}

std::shared_ptr<StorageTexture> cachedStorageTexture(const Context& context, std::span<const std::uint32_t> words, const GuestTextureResource& resource, std::uint32_t mip, std::uint64_t guestBytes) {
if (DepthSurfaceAt(resource.baseAddress)) {
char text[112];
Expand Down Expand Up @@ -1630,6 +1638,7 @@ bool ShaderResources::Revalidate(std::span<const CompiledShader> shaders, ProofR
for (std::uint32_t element = 0; element < binding.count; ++element) {
const auto words = std::span<const std::uint32_t>(binding.guestDescriptor).subspan(static_cast<std::size_t>(element) * elementWords, elementWords);
const auto resource = DecodeTextureResource(words);
RequireLevelsPresent(resource);
const VkComponentMapping components{ComponentSwizzleFor(resource.dstSelX), ComponentSwizzleFor(resource.dstSelY), ComponentSwizzleFor(resource.dstSelZ), ComponentSwizzleFor(resource.dstSelW)};
if (textureIndex >= textures.size() || cachedTexture(context, words, resource, components, 0, !binding.imageDepthCompare.empty() && binding.imageDepthCompare.at(element)) != textures[textureIndex]) return false;
++textureIndex;
Expand Down Expand Up @@ -2453,6 +2462,7 @@ void ShaderResources::resolveImageBinding(const ShaderRecompiler::DescriptorBind
const auto words = std::span<const std::uint32_t>(binding.guestDescriptor).subspan(static_cast<std::size_t>(element) * elementWords, elementWords);
const auto* record = nextRecord();
const auto resource = record != nullptr && record->decoded ? record->resource : DecodeTextureResource(words);
RequireLevelsPresent(resource);
const bool firstLayer = binding.imageShape == ShaderRecompiler::DescriptorImageShape::Image2D && resource.dimension == TextureDimension::k2DArray;
if (!firstLayer && !MatchesGuestDimension(*binding.imageShape, resource.dimension)) throw std::runtime_error("AGC graphics: guest texture dimension disagrees with the shader's declared image shape (shape " + std::to_string(static_cast<int>(*binding.imageShape)) + ", dimension " + std::to_string(static_cast<int>(resource.dimension)) + ")");
const VkComponentMapping components{ComponentSwizzleFor(resource.dstSelX), ComponentSwizzleFor(resource.dstSelY), ComponentSwizzleFor(resource.dstSelZ), ComponentSwizzleFor(resource.dstSelW)};
Expand Down Expand Up @@ -2494,7 +2504,11 @@ void ShaderResources::resolveImageBinding(const ShaderRecompiler::DescriptorBind
storageKeys.push_back(resource.dccAddress);
storageFirstLayer.push_back(firstLayer);
// Images the shader only reads have nothing to store back.
storageWritten.push_back(element >= binding.imageWritten.size() || binding.imageWritten[element]);
if (resource.emptyView) {
Require(sameAsPrevious == false && mipOffset == 0, "AGC graphics: dynamic-mip storage writes to an image view past its surface's last mip level are not implemented");
NoteDroppedStores(resource);
}
storageWritten.push_back(!resource.emptyView && (element >= binding.imageWritten.size() || binding.imageWritten[element]));
describedRanges.push_back({"storage", resource.baseAddress, guestBytes, resource.width, resource.height, resource.format, static_cast<int>(resource.tileMode), resource.dccAddress});
item.imageAllocations.push_back(storageTextures.size() - 1);
}
Expand Down
16 changes: 16 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,22 @@ void RunGuestTextureResourceTests() {
const auto partial = DecodeTextureResource(pack(partialMips));
Require(partial.lastLevel == 1 && partial.mipCount == 3, "a view over part of the mip chain decoded wrongly");

Fields pastLast = base;
pastLast.width = 64;
pastLast.height = 64;
pastLast.maxMip = 5;
pastLast.baseLevel = 6;
pastLast.lastLevel = 6;
const auto empty = DecodeTextureResource(pack(pastLast));
Require(empty.emptyView && empty.emptyBaseLevel == 6, "a view starting past MAX_MIP was not marked empty");
Require(empty.baseLevel == 5 && empty.lastLevel == 5 && empty.mipCount == 6, "an empty view was not clamped to the surface's last mip level");
reject([&] { RequireLevelsPresent(empty); }, "starts past the surface's last mip level");
pastLast.baseLevel = 5;
pastLast.lastLevel = 5;
const auto lastLevel = DecodeTextureResource(pack(pastLast));
Require(!lastLevel.emptyView && lastLevel.baseLevel == 5 && lastLevel.emptyBaseLevel == 0, "a view of the surface's last mip level was marked empty");
RequireLevelsPresent(lastLevel);

std::array<std::uint32_t, 4> shortWords{};
reject([&] { DecodeTextureResource(shortWords); }, "8 dwords");

Expand Down
127 changes: 127 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/execution/EmptyMipView.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libc/include/GuestAllocations.hpp"
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp"
#include "Recompiler.hpp"
#include "VulkanTestDevice.hpp"
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstdio>
#include <iostream>
#include <mutex>
#include <span>
#include <stdexcept>
#include <string>
#include <string_view>
#include <vector>

namespace {

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

constexpr std::uint32_t Threads = 8;
constexpr std::uint32_t Side = 8;
constexpr std::uint32_t Format32UInt = 20;
constexpr std::uint32_t Type2D = 9;
constexpr std::uint32_t Stored = 0x12345678u;
constexpr std::uint32_t Untouched = 0xdeadbeefu;
alignas(4096) std::array<std::uint32_t, 4096> Texels{};

alignas(256) constexpr std::array<std::uint32_t, 7> StoreCode{
0x7e040300, 0x7e060280, 0x7e0202ff, Stored, 0xf0200108, 0x00020102, 0xbf810000,
};

alignas(256) constexpr std::array<std::uint32_t, 8> LoadCode{
0x7e040300, 0x7e060280, 0xf0000108, 0x00020102, 0xbf8c3f70, 0xf0200108, 0x00020102, 0xbf810000,
};

std::array<std::uint32_t, 8> TextureDescriptor(std::uint32_t baseLevel, std::uint32_t lastLevel, std::uint32_t maxMip) {
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) | (Format32UInt << 20u) | (((Side - 1u) & 3u) << 30u),
((Side - 1u) >> 2u) | ((Side - 1u) << 14u),
0xfacu | (baseLevel << 12u) | (lastLevel << 16u) | (Type2D << 28u),
0u,
maxMip << 4u,
0u,
0u,
};
}

bool RunStore(AgcDriver::VulkanDevice& device, std::uint32_t baseLevel) {
Texels.fill(Untouched);
std::vector<std::uint32_t> userData(16, 0u);
const auto texture = TextureDescriptor(baseLevel, baseLevel, 1u);
std::copy(texture.begin(), texture.end(), userData.begin() + 8);
const std::span<const std::uint32_t> code(StoreCode);
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();
{
std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex());
AgcDriver::Graphics::FlushCachedTextures(device.Device());
}
device.WaitIdle();
return std::any_of(Texels.begin(), Texels.end(), [](std::uint32_t texel) { return texel != Untouched; });
}

void RunLoad(AgcDriver::VulkanDevice& device) {
std::vector<std::uint32_t> userData(16, 0u);
const auto texture = TextureDescriptor(2u, 2u, 1u);
std::copy(texture.begin(), texture.end(), userData.begin() + 8);
const std::span<const std::uint32_t> code(LoadCode);
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;
try {
static_cast<void>(ShaderRecompiler::Recompile(request));
} catch (const std::exception& error) {
Require(std::string_view(error.what()).find("past its surface's last mip level") != std::string_view::npos, std::string("load from an empty view failed for another reason: ") + error.what());
return;
}
Require(false, "load from an image view past the surface's last mip level compiled");
}

void RegisterTexels(bool add) {
auto* mutation = GuestAllocations::GuestAllocationsBegin_nid_postfix();
if (add) GuestAllocations::GuestAllocationsAdd_nid_postfix(mutation, Texels.data(), sizeof(Texels), true, true);
else GuestAllocations::GuestAllocationsRemove_nid_postfix(mutation, Texels.data());
GuestAllocations::GuestAllocationsEnd_nid_postfix(mutation);
}

}

int main() {
try {
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
RegisterTexels(true);
Require(RunStore(*device, 1u), "store through the surface's last mip level did not reach the texels");
Require(!RunStore(*device, 2u), "store through a view past the surface's last mip level changed the texels");
RunLoad(*device);
RegisterTexels(false);
std::puts("empty mip view tests passed");
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ struct ImageResource {
bool r128 = false;
bool depthBits = false;
bool depthUnorm16 = false;
bool empty = false;
std::uint32_t indirectRoot = NoIndirectImage;
std::uint32_t indirectMappingOffset = 0;
std::uint32_t indirectSearchIterations = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ struct ResourceSpecialization {
bool fmask = false;
bool depthBits = false;
bool depthUnorm16 = false;
bool empty = false;

bool operator==(const Image& other) const;
};
Expand Down
30 changes: 24 additions & 6 deletions core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ struct DecodedImage {
bool fmask = false;
bool depthBits = false;
bool depthUnorm16 = false;
bool empty = false;
};

ShaderBufferResource decodeBufferDescriptor(const DescriptorValue& value) {
Expand All @@ -57,6 +58,18 @@ ShaderBufferResource decodeBufferDescriptor(const DescriptorValue& value) {
return result;
}

std::uint32_t imageBaseLevel(const DescriptorValue& descriptor) {
return (descriptor.dwords[3] >> 12u) & 0xfu;
}

std::uint32_t imageLastLevel(const DescriptorValue& descriptor) {
return (descriptor.dwords[3] >> 16u) & 0xfu;
}

std::uint32_t imageMaxMip(const DescriptorValue& descriptor) {
return (descriptor.dwords[5] >> 4u) & 0xfu;
}

bool nullImageDescriptor(const DescriptorValue& descriptor) {
return descriptor.dwords[0] == 0u && (descriptor.dwords[1] & 0xffu) == 0u;
}
Expand Down Expand Up @@ -111,9 +124,9 @@ bool validImageDescriptor(const DescriptorValue& descriptor, bool r128) {
return false;
}
if (type == ImageType::Color2DMsaa || type == ImageType::Color2DMsaaArray) {
const auto baseLevel = (descriptor.dwords[3] >> 12u) & 0xfu;
const auto fragments = (descriptor.dwords[3] >> 16u) & 0xfu;
const auto maxMip = (descriptor.dwords[5] >> 4u) & 0xfu;
const auto baseLevel = imageBaseLevel(descriptor);
const auto fragments = imageLastLevel(descriptor);
const auto maxMip = imageMaxMip(descriptor);
return baseLevel == 0u && fragments >= 1u && fragments <= 3u && (r128 || maxMip == fragments);
}
return true;
Expand All @@ -123,8 +136,8 @@ std::uint32_t storageMipCount(const ImageResource& base, const DescriptorValue&
if (base.mipMode != ImageMipMode::DynamicStorage || nullImageDescriptor(descriptor)) {
return 1u;
}
const auto mipBase = (descriptor.dwords[3] >> 12u) & 0xfu;
const auto mipLast = (descriptor.dwords[3] >> 16u) & 0xfu;
const auto mipBase = imageBaseLevel(descriptor);
const auto mipLast = imageLastLevel(descriptor);
return mipBase <= mipLast ? mipLast - mipBase + 1u : 0u;
}

Expand All @@ -140,6 +153,9 @@ DecodedImage decodeImageDescriptor(const DescriptorValue& descriptor, const Imag
decoded.cube = false;
return decoded;
}
if (descriptor.dwordCount == 8u && base.mipMode != ImageMipMode::DynamicStorage) {
decoded.empty = imageBaseLevel(descriptor) > imageMaxMip(descriptor);
}
if (base.resourceClass == ImageResourceClass::None || (base.atomic && base.resourceClass != ImageResourceClass::Storage)) {
throw std::runtime_error("image resource has an invalid class");
}
Expand Down Expand Up @@ -533,6 +549,7 @@ void buildResourceSpecialization(const IrResourcePlan& plan, ResourceSnapshot& s
entry.fmask = decoded.fmask;
entry.depthBits = decoded.depthBits;
entry.depthUnorm16 = decoded.depthUnorm16;
entry.empty = decoded.empty;
result.images.push_back(entry);
}

Expand Down Expand Up @@ -619,6 +636,7 @@ void ResourceMaterializer::Apply(IrProgram& program, const ResourceSpecializatio
image.cube = source.cube;
image.depthBits = source.depthBits;
image.depthUnorm16 = source.depthUnorm16;
image.empty = source.empty;
image.indirectResources.clear();
}
for (std::uint32_t index = 0; index < images.size(); index++) {
Expand Down Expand Up @@ -884,7 +902,7 @@ bool ResourceSpecialization::Buffer::operator==(const Buffer& other) const {
}

bool ResourceSpecialization::Image::operator==(const Image& other) const {
return numericClass == other.numericClass && dimension == other.dimension && mipCount == other.mipCount && conversionFormat == other.conversionFormat && shaderSwizzle == other.shaderSwizzle && indirectRoot == other.indirectRoot && indirectMappingOffset == other.indirectMappingOffset && indirectSearchIterations == other.indirectSearchIterations && cube == other.cube && fmask == other.fmask && depthBits == other.depthBits && depthUnorm16 == other.depthUnorm16;
return numericClass == other.numericClass && dimension == other.dimension && mipCount == other.mipCount && conversionFormat == other.conversionFormat && shaderSwizzle == other.shaderSwizzle && indirectRoot == other.indirectRoot && indirectMappingOffset == other.indirectMappingOffset && indirectSearchIterations == other.indirectSearchIterations && cube == other.cube && fmask == other.fmask && depthBits == other.depthBits && depthUnorm16 == other.depthUnorm16 && empty == other.empty;
}

bool ResourceSpecialization::operator==(const ResourceSpecialization& other) const {
Expand Down
Loading
Loading