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
@@ -1,6 +1,7 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_COLORTARGETLAYOUT_HPP
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_COLORTARGETLAYOUT_HPP

#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp"
#include <cstddef>
#include <cstdint>
#include <span>
Expand All @@ -10,17 +11,22 @@ namespace AgcDriver::Graphics {

enum class ColorTileMode : std::uint32_t {
Linear = 0,
Standard4KB = 5,
RenderTarget = 0x1b
};

constexpr TextureTileMode ColorTextureTileMode(ColorTileMode mode) {
return mode == ColorTileMode::Linear ? TextureTileMode::kLinear : mode == ColorTileMode::Standard4KB ? TextureTileMode::kStandard4KB : TextureTileMode::kR64KBX;
}

ColorTileMode DecodeColorTileMode(std::uint32_t attrib3);

class ColorTargetLayout {
public:
ColorTargetLayout(std::uint32_t width, std::uint32_t height, ColorTileMode mode, std::uint32_t bytesPerElement = 4);
std::size_t Bytes() const { return bytes; }
std::size_t LinearBytes() const { return static_cast<std::size_t>(width) * height * elementBytes; }
std::size_t Alignment() const { return mode == ColorTileMode::Linear ? 256u : 65536u; }
std::size_t Alignment() const { return mode == ColorTileMode::Linear ? 256u : mode == ColorTileMode::Standard4KB ? 4096u : 65536u; }
std::uint32_t BlocksPerRow() const { return pitch / blockWidth; }
std::size_t Offset(std::uint32_t x, std::uint32_t y) const;
void Detile(std::span<const std::byte> source, std::span<std::byte> destination) const;
Expand Down
2 changes: 2 additions & 0 deletions core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ struct ColorTarget {
bool mipTail = false;
std::array<std::uint32_t, 2> clearWords{};
std::uint32_t slot = 0;
std::uint32_t depth = 1;
std::uint32_t depthSlice = 0;
};

struct DepthTarget {
Expand Down
5 changes: 3 additions & 2 deletions core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <map>
#include <memory>
#include <span>
#include <tuple>
#include <utility>
#include <vector>

Expand Down Expand Up @@ -116,7 +117,7 @@ class StorageTexture : public std::enable_shared_from_this<StorageTexture> {
// Render targets live in the same images: draws attach mip 0 through a view of the color
// buffer's format and mark the image dirty like a storage write.
bool Attachable() const { return attachable; }
VkImageView AttachmentView(VkFormat format, std::uint32_t mip = 0);
VkImageView AttachmentView(VkFormat format, std::uint32_t mip = 0, std::uint32_t depthSlice = 0);
void WriteBack();
// Deferred write-back (APS5_EAGER_WRITEBACK=1 stores at once instead).
void MarkDirty();
Expand Down Expand Up @@ -421,7 +422,7 @@ class StorageTexture : public std::enable_shared_from_this<StorageTexture> {
std::map<std::uint32_t, VkImageView> extraViews;
std::map<std::uint32_t, VkImageView> firstLayerViews;
bool attachable = false;
std::map<std::pair<VkFormat, std::uint32_t>, VkImageView> attachmentViews;
std::map<std::tuple<VkFormat, std::uint32_t, std::uint32_t>, VkImageView> attachmentViews;
VkFormat storageFormat = VK_FORMAT_UNDEFINED;
// Results are on the GPU only (guarded by the pending-write registry lock).
bool dirty = false;
Expand Down
42 changes: 39 additions & 3 deletions core/libs/prx/libSceAgcDriver/Graphics/src/ColorTargetLayout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,25 @@ std::uint32_t parity(std::uint32_t value) {
return static_cast<std::uint32_t>(std::popcount(value)) & 1u;
}

std::uint32_t standardOffset(std::uint32_t x, std::uint32_t y, std::uint32_t elementBytes) {
switch (elementBytes) {
case 1u: return ((y << 4) & 0x1f0u) ^ ((y << 5) & 0x400u) ^ (x & 0x00fu) ^ ((x << 5) & 0x200u) ^ ((x << 6) & 0x800u);
case 2u: return ((y << 4) & 0x070u) ^ ((y << 5) & 0x100u) ^ ((y << 6) & 0x400u) ^ ((x << 1) & 0x00eu) ^ ((x << 4) & 0x080u) ^ ((x << 5) & 0x200u) ^ ((x << 6) & 0x800u);
case 4u: return ((y << 4) & 0x070u) ^ ((y << 5) & 0x100u) ^ ((y << 6) & 0x400u) ^ ((x << 2) & 0x00cu) ^ ((x << 5) & 0x080u) ^ ((x << 6) & 0x200u) ^ ((x << 7) & 0x800u);
case 8u: return ((y << 4) & 0x030u) ^ ((y << 6) & 0x100u) ^ ((y << 7) & 0x400u) ^ ((x << 3) & 0x008u) ^ ((x << 5) & 0x0c0u) ^ ((x << 6) & 0x200u) ^ ((x << 7) & 0x800u);
default: return ((y << 4) & 0x030u) ^ ((y << 6) & 0x100u) ^ ((y << 7) & 0x400u) ^ ((x << 6) & 0x0c0u) ^ ((x << 7) & 0x200u) ^ ((x << 8) & 0x800u);
}
}

}

ColorTileMode DecodeColorTileMode(std::uint32_t attrib3) {
require((attrib3 & 0x80002000u) == 0 && (attrib3 & 0x1fffu) == 0 && ((attrib3 >> 24u) & 3u) == 1 && ((attrib3 >> 27u) & 7u) == 1, "AGC graphics: unsupported color depth, dimension, resource level or metadata mode");
const auto resourceType = (attrib3 >> 24u) & 3u;
require((attrib3 & 0x80002000u) == 0 && ((resourceType == 1 && (attrib3 & 0x1fffu) == 0) || resourceType == 2) && ((attrib3 >> 27u) & 7u) == 1, "AGC graphics: unsupported color depth, dimension, resource level or metadata mode");
const auto mode = (attrib3 >> 14u) & 0x1fu;
const auto fmaskMode = (attrib3 >> 19u) & 0x1fu;
require(fmaskMode == 0 || fmaskMode == 0x18, "AGC graphics: unsupported color FMASK swizzle mode");
require(mode == 0 || mode == 0x1b, "AGC graphics: unsupported color tile mode");
require(mode == 0 || mode == 5 || mode == 0x1b, "AGC graphics: unsupported color tile mode");
return static_cast<ColorTileMode>(mode);
}

Expand Down Expand Up @@ -59,6 +70,20 @@ const SwizzleTables& renderTargetTables(std::uint32_t bytesPerElement, std::uint
return tables[index];
}

const SwizzleTables& standardTables(std::uint32_t bytesPerElement, std::uint32_t blockWidth, std::uint32_t blockHeight) {
static std::once_flag once[5];
static SwizzleTables tables[5];
const auto index = static_cast<std::size_t>(std::countr_zero(bytesPerElement));
std::call_once(once[index], [&] {
auto& table = tables[index];
table.x.resize(blockWidth);
table.y.resize(blockHeight);
for (std::uint32_t x = 0; x < blockWidth; ++x) table.x[x] = standardOffset(x, 0, bytesPerElement);
for (std::uint32_t y = 0; y < blockHeight; ++y) table.y[y] = standardOffset(0, y, bytesPerElement);
});
return tables[index];
}

}

ColorTargetLayout::ColorTargetLayout(std::uint32_t width, std::uint32_t height, ColorTileMode mode, std::uint32_t bytesPerElement) : width(width), height(height), pitch(width), mode(mode), bytes(0), elementBytes(bytesPerElement) {
Expand All @@ -83,6 +108,17 @@ ColorTargetLayout::ColorTargetLayout(std::uint32_t width, std::uint32_t height,
yOffsets = tables.y.data();
break;
}
case ColorTileMode::Standard4KB: {
const auto log2Bytes = static_cast<std::uint32_t>(std::countr_zero(bytesPerElement));
blockWidth = 1u << (6u - (log2Bytes + 1u) / 2u);
blockHeight = 1u << (6u - log2Bytes / 2u);
pitch = (width + blockWidth - 1u) / blockWidth * blockWidth;
paddedHeight = (height + blockHeight - 1u) / blockHeight * blockHeight;
const auto& tables = standardTables(bytesPerElement, blockWidth, blockHeight);
xOffsets = tables.x.data();
yOffsets = tables.y.data();
break;
}
default: throw std::runtime_error("AGC graphics: unsupported color tile mode");
}
const auto size = static_cast<std::uint64_t>(pitch) * paddedHeight * bytesPerElement;
Expand All @@ -93,7 +129,7 @@ ColorTargetLayout::ColorTargetLayout(std::uint32_t width, std::uint32_t height,
std::size_t ColorTargetLayout::offset(std::uint32_t x, std::uint32_t y) const {
if (mode == ColorTileMode::Linear) return (static_cast<std::size_t>(y) * pitch + x) * elementBytes;
const auto block = static_cast<std::size_t>(y / blockHeight) * (pitch / blockWidth) + x / blockWidth;
return block * 65536u + (xOffsets[x % blockWidth] ^ yOffsets[y % blockHeight]);
return block * Alignment() + (xOffsets[x % blockWidth] ^ yOffsets[y % blockHeight]);
}

std::size_t ColorTargetLayout::Offset(std::uint32_t x, std::uint32_t y) const {
Expand Down
17 changes: 9 additions & 8 deletions core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,19 @@ std::uint32_t GuestFormatFor(VkFormat format, std::uint32_t elementBytes) {

// The color buffer as a single-mip 2D surface descriptor (tile mode SW_64KB_R_X).
GuestTextureResource SurfaceForTarget(const ColorTarget& color) {
Require(color.tileMode == ColorTileMode::RenderTarget, "only 64 KiB tiled color targets are resident");
Require(color.tileMode == ColorTileMode::RenderTarget || color.tileMode == ColorTileMode::Standard4KB, "only 4 KiB standard and 64 KiB tiled color targets are resident");
const bool chain = color.mipCount > 1;
GuestTextureResource surface{};
surface.baseAddress = chain ? color.surfaceAddress : color.address;
surface.width = chain ? color.surfaceExtent.width : color.extent.width;
surface.height = chain ? color.surfaceExtent.height : color.extent.height;
surface.depthOrLastArray = 0;
surface.depthOrLastArray = color.depth - 1u;
surface.baseArray = 0;
surface.mipCount = color.mipCount;
surface.baseLevel = 0;
surface.lastLevel = color.mipCount - 1;
surface.tileMode = TextureTileMode::kR64KBX;
surface.dimension = TextureDimension::k2D;
surface.tileMode = ColorTextureTileMode(color.tileMode);
surface.dimension = color.depth > 1 ? TextureDimension::k3D : TextureDimension::k2D;
surface.format = GuestFormatFor(color.format, color.elementBytes);
surface.dstSelX = 4;
surface.dstSelY = 5;
Expand Down Expand Up @@ -1508,7 +1508,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters&
timer.phase(PhaseSetup);
// Debug aid: APS5_NO_RESIDENT_TARGETS=1 copies every target in and out again.
static const bool residentTargets = std::getenv("APS5_NO_RESIDENT_TARGETS") == nullptr;
if (binding.gpuTiling && residentTargets) {
if ((binding.gpuTiling || (color.tileMode == ColorTileMode::Standard4KB && context.detiler != nullptr)) && residentTargets) {
// The lookup refreshes the image on every draw (StorageTexture::Refresh: FlushPending,
// CollectWrites over the target's pages, the DCC key scan of TextureClearKeys, then
// UnchangedSince). The page walk is skipped while the worker's collect epoch lasts
Expand All @@ -1518,16 +1518,17 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters&
binding.resident = refreshResidentTarget(context, state, color, outcome, profile, [&] {
auto resident = CachedStorageSurface(context, SurfaceForTarget(color));
Require(resident->Attachable(), "storage format cannot be a color attachment");
Require(color.mipCount > 1 || resident->GuestBytes() == colorLayout.Bytes(), "resident image layout differs from the color layout");
Require(color.mipCount > 1 || color.depth > 1 || resident->GuestBytes() == colorLayout.Bytes(), "resident image layout differs from the color layout");
return resident;
});
}
if (binding.resident != nullptr) {
timer.phase(PhaseReadTarget);
targetViews.push_back(binding.resident->AttachmentView(color.format, color.mip));
targetViews.push_back(binding.resident->AttachmentView(color.format, color.mip, color.depthSlice));
continue;
}
Require(!color.mipTail, "rendering into a packed mip tail needs the resident image of its surface");
Require(color.depth == 1, "rendering into a 3D color target needs the resident image of its surface");
if (binding.gpuTiling) {
binding.mip = ColorTargetMip(color, colorLayout);
binding.tiled = std::make_unique<Buffer>(context, colorLayout.Bytes(), copies);
Expand Down Expand Up @@ -2074,7 +2075,7 @@ void RunColorMetadataPass(const Context& context, const ColorMetadataPass& pass)
Require(IsDccClear(keys), std::string("CB metadata pass over DCC keys that are ") + DccKeysName(keys) + " (per-block metadata is not modeled)");
const auto texel = clearTexel(color, keys);
std::shared_ptr<StorageTexture> resident;
if (color.tileMode == ColorTileMode::RenderTarget && context.detiler != nullptr) {
if ((color.tileMode == ColorTileMode::RenderTarget || color.tileMode == ColorTileMode::Standard4KB) && context.detiler != nullptr) {
try {
resident = CachedStorageSurface(context, SurfaceForTarget(color));
} catch (const std::exception&) {
Expand Down
14 changes: 12 additions & 2 deletions core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -619,14 +619,23 @@ ColorTarget DecodeColorBuffer(const Registers& cx, std::uint32_t slot) {
if ((info & ~(0x00039f7cu | 0x00040000u | 0x10000000u)) != 0) throw std::runtime_error("AGC graphics: color compression, DCC, endian conversion, nonstandard rounding or color optimization is unsupported (CB_COLOR_INFO 0x" + [&] { char text[16]; std::snprintf(text, sizeof(text), "%08x", info); return std::string(text); }() + ")");
Require((info & 0x8000u) != 0 || number == 7 || number == 4 || number == 5, "unclamped normalized color is unsupported");
const auto view = read(cx, 0x31b + stride);
Require((view & ~0x3c000000u) == 0, "color array views are unsupported");
Require((view & ~0x3fffffffu) == 0, "reserved CB_COLOR_VIEW bits are set");
const auto slice = view & 0x1fffu;
Require(slice == ((view >> 13u) & 0x1fffu), "color views of several array slices are unsupported");
const auto viewMip = (view >> 26u) & 0xfu;
zero(cx, 0x31d + stride, ~0u, "color samples, fragments or destination alpha override");
const auto attrib2 = read(cx, 0x3b0 + slot);
const auto maxMip = attrib2 >> 28u;
Require(viewMip <= maxMip, "color view mip exceeds the surface");
const auto attrib3 = read(cx, 0x3b8 + slot);
color.tileMode = DecodeColorTileMode(attrib3);
const bool volume = ((attrib3 >> 24u) & 3u) == 2u;
if (volume) {
color.depth = (attrib3 & 0x1fffu) + 1u;
Require(maxMip == 0 && (info & 0x10000000u) == 0, "mipmapped or DCC 3D color targets are unsupported");
Require(slice < color.depth, "the color view slice is beyond the 3D surface");
color.depthSlice = slice;
}
color.extent = {((attrib2 >> 14u) & 0x3fffu) + 1u, (attrib2 & 0x3fffu) + 1u};
color.elementBytes = decoded.elementBytes;
std::uint64_t mipOffset = 0;
Expand All @@ -635,7 +644,7 @@ ColorTarget DecodeColorBuffer(const Registers& cx, std::uint32_t slot) {
color.mip = viewMip;
if (maxMip != 0) {
// A mipmapped surface is addressed like a texture; the view renders into one mip of it.
const auto mips = ComputeElementMipLayout(color.tileMode == ColorTileMode::Linear ? TextureTileMode::kLinear : TextureTileMode::kR64KBX, color.elementBytes, color.extent.width, color.extent.height, maxMip + 1u);
const auto mips = ComputeElementMipLayout(ColorTextureTileMode(color.tileMode), color.elementBytes, color.extent.width, color.extent.height, maxMip + 1u);
const auto& mip = mips.at(viewMip);
mipOffset = mip.tiledOffset;
color.mipTail = mip.tail;
Expand All @@ -645,6 +654,7 @@ ColorTarget DecodeColorBuffer(const Registers& cx, std::uint32_t slot) {
const auto high = read(cx, 0x390 + slot);
Require((high & ~0xffu) == 0, "invalid color address extension");
color.surfaceAddress = (static_cast<std::uint64_t>(high) << 40u) | (static_cast<std::uint64_t>(read(cx, 0x318 + stride)) << 8u);
if (slice != 0 && !volume) color.surfaceAddress += slice * ComputeSurfaceSize(ComputeElementMipLayout(ColorTextureTileMode(color.tileMode), color.elementBytes, color.surfaceExtent.width, color.surfaceExtent.height, color.mipCount), 1);
color.address = color.surfaceAddress + mipOffset;
color.bytes = colorLayout.Bytes();
GuestMemory::CheckRange(reinterpret_cast<const void*>(color.address), color.bytes, colorLayout.Alignment(), true);
Expand Down
14 changes: 8 additions & 6 deletions core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,7 @@ StorageTexture::StorageTexture(const Context& context, TextureDetiler& detiler,

VkImageCreateInfo imageInfo{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
// Sampled views of other same-size formats (sRGB, reinterpretations) read the image directly.
imageInfo.flags = (descriptor.dimension == TextureDimension::kCube ? VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT : 0u) | VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT | VK_IMAGE_CREATE_EXTENDED_USAGE_BIT;
imageInfo.flags = (descriptor.dimension == TextureDimension::kCube ? VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT : 0u) | (descriptor.dimension == TextureDimension::k3D ? VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT : 0u) | VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT | VK_IMAGE_CREATE_EXTENDED_USAGE_BIT;
imageInfo.imageType = ImageTypeFor(descriptor.dimension);
imageInfo.format = vkFormat;
imageInfo.extent = {descriptor.width, descriptor.height, geometry.imageDepth};
Expand All @@ -675,7 +675,7 @@ StorageTexture::StorageTexture(const Context& context, TextureDetiler& detiler,
{
VkFormatProperties properties{};
context.formatProperties(context.physical, vkFormat, &properties);
attachable = descriptor.dimension == TextureDimension::k2D && (properties.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) != 0;
attachable = (descriptor.dimension == TextureDimension::k2D || descriptor.dimension == TextureDimension::k3D) && (properties.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) != 0;
if (attachable) imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
}
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
Expand Down Expand Up @@ -958,10 +958,12 @@ VkImageView StorageTexture::createView(std::uint32_t mip, bool firstLayer) const
return created;
}

VkImageView StorageTexture::AttachmentView(VkFormat format, std::uint32_t mip) {
VkImageView StorageTexture::AttachmentView(VkFormat format, std::uint32_t mip, std::uint32_t depthSlice) {
Require(attachable, "storage image cannot be a color attachment");
Require(mip < descriptor.mipCount, "attachment mip exceeds the storage image");
const auto found = attachmentViews.find({format, mip});
const bool volume = descriptor.dimension == TextureDimension::k3D;
Require(depthSlice == 0 || (volume && mip == 0 && depthSlice <= descriptor.depthOrLastArray), "attachment slice is outside the storage image");
const auto found = attachmentViews.find({format, mip, depthSlice});
if (found != attachmentViews.end()) return found->second;
VkImageViewUsageCreateInfo usage{VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO};
usage.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
Expand All @@ -971,10 +973,10 @@ VkImageView StorageTexture::AttachmentView(VkFormat format, std::uint32_t mip) {
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
viewInfo.format = format;
viewInfo.components = {VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY};
viewInfo.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, mip, 1u, descriptor.baseArray, 1u};
viewInfo.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, mip, 1u, volume ? depthSlice : descriptor.baseArray, 1u};
VkImageView created = VK_NULL_HANDLE;
Check(context.Function<PFN_vkCreateImageView>("vkCreateImageView")(context.device, &viewInfo, nullptr, &created), "vkCreateImageView attachment");
attachmentViews.emplace(std::pair{format, mip}, created);
attachmentViews.emplace(std::tuple{format, mip, depthSlice}, created);
return created;
}

Expand Down
Loading
Loading