mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-06 13:30:00 +00:00
Merge pull request #6973 from Amoenus/codex/feat-msaa-initial-samples-main402
feat(agc): preserve initial native multisample color contents
This commit is contained in:
14 files changed
+546
-107
No files matched your search
@@ -68,7 +68,7 @@ public:
|
||||
// compute pass writing it and the next pass sampling it share one image and copy nothing.
|
||||
// CanCopyFrom says whether the two descriptors address the same surface compatibly.
|
||||
Texture(const Context& context, const std::shared_ptr<StorageTexture>& source, const GuestTextureResource& descriptor, VkComponentMapping components);
|
||||
Texture(const Context& context, VkImage depthImage, VkFormat depthFormat, VkImageAspectFlags aspect, VkComponentMapping components, VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D, std::uint32_t baseLayer = 0, std::shared_ptr<OwnedImage> owner = {}, VkImageUsageFlags usage = 0, std::uint32_t layerCount = VK_REMAINING_ARRAY_LAYERS);
|
||||
Texture(const Context& context, VkImage depthImage, VkFormat depthFormat, VkImageAspectFlags aspect, VkComponentMapping components, VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D, std::uint32_t baseLayer = 0, std::shared_ptr<OwnedImage> owner = {}, VkImageUsageFlags usage = 0, std::uint32_t layerCount = VK_REMAINING_ARRAY_LAYERS, VkImageLayout imageLayout = VK_IMAGE_LAYOUT_GENERAL);
|
||||
struct DepthCopyTag {};
|
||||
Texture(const Context& context, const std::shared_ptr<StorageTexture>& source, const GuestTextureResource& descriptor, VkComponentMapping components, DepthCopyTag);
|
||||
struct SrgbExpandTag {};
|
||||
|
||||
@@ -195,10 +195,11 @@ struct TargetUse {
|
||||
|
||||
class MultisampleTarget : public std::enable_shared_from_this<MultisampleTarget> {
|
||||
public:
|
||||
MultisampleTarget(const Context& context, const ColorTarget& target, bool guestSamples = false) : context(context), target(target), cmask(target.cmaskAddress, target.cmaskBytes), colorFill(target) {
|
||||
MultisampleTarget(const Context& context, const ColorTarget& target, std::optional<VkClearColorValue> knownClear = std::nullopt, bool guestSamples = false) : context(context), target(target), cmask(target.cmaskAddress, target.cmaskBytes), colorFill(target) {
|
||||
this->context.bufferPool.reset();
|
||||
if (target.dccAddress != 0) metadataValid = std::make_shared<std::atomic<bool>>(true);
|
||||
const auto initial = initialDccClear(target);
|
||||
std::shared_ptr<Buffer> initialUpload;
|
||||
const auto initial = guestSamples ? std::optional<VkClearColorValue>{} : knownClear.has_value() ? knownClear : target.dccAddress != 0 || target.cmaskFastClear ? std::optional{initialDccClear(target)} : initialColorContents(context, target, initialUpload);
|
||||
if (target.dccAddress != 0) cleared = initial;
|
||||
const auto samples = static_cast<VkSampleCountFlagBits>(target.samples);
|
||||
Require((context.limits.framebufferColorSampleCounts & samples) != 0, "the device cannot render color with the target's sample count");
|
||||
@@ -219,7 +220,7 @@ public:
|
||||
storageFlags = storageFormat != target.format || (uintFormat != VK_FORMAT_UNDEFINED && uintFormat != target.format) ? VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT | VK_IMAGE_CREATE_EXTENDED_USAGE_BIT : 0u;
|
||||
const auto storageSupports = [&](VkFormat format, VkImageUsageFlags formatUsage, VkImageCreateFlags flags) {
|
||||
VkImageFormatProperties properties{};
|
||||
return context.imageFormatProperties(context.physical, format, VK_IMAGE_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, formatUsage, flags, &properties) == VK_SUCCESS && (properties.sampleCounts & samples) != 0;
|
||||
return context.imageFormatProperties(context.physical, format, VK_IMAGE_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, formatUsage, flags, &properties) == VK_SUCCESS && (properties.sampleCounts & samples) != 0 && target.extent.width <= properties.maxExtent.width && target.extent.height <= properties.maxExtent.height && properties.maxArrayLayers != 0;
|
||||
};
|
||||
const auto uintStorageSupported = [&] {
|
||||
if (uintFormat == VK_FORMAT_UNDEFINED) return true;
|
||||
@@ -229,17 +230,16 @@ public:
|
||||
};
|
||||
if (storageFormat != VK_FORMAT_UNDEFINED && (!context.storageImageMultisample || !storageSupports(storageFormat, VK_IMAGE_USAGE_STORAGE_BIT, 0u) || !storageSupports(target.format, usage | VK_IMAGE_USAGE_STORAGE_BIT, storageFlags) || !uintStorageSupported())) storageFormat = VK_FORMAT_UNDEFINED;
|
||||
VkImageCreateFlags flags = orderFlags;
|
||||
std::shared_ptr<Buffer> upload;
|
||||
const auto elementFormat = guestSamples ? TextureDetiler::ImageElementFormat(target.elementBytes) : VK_FORMAT_UNDEFINED;
|
||||
const auto alignment = std::max<VkDeviceSize>({4u, target.elementBytes, context.limits.minStorageBufferOffsetAlignment});
|
||||
const auto stride = context.limits.maxStorageBufferRange / alignment * alignment;
|
||||
if (guestSamples) {
|
||||
Require(context.detiler != nullptr && context.storageImageMultisample && context.storageImageWriteWithoutFormat, "uploading multisampled guest color requires formatless multisampled storage image writes");
|
||||
Require(stride != 0, "uploading multisampled guest color needs a storage range of at least one aligned element");
|
||||
if (!initial.has_value()) {
|
||||
Require(stride != 0, "initial multisample storage range cannot hold an aligned element");
|
||||
Require(context.detiler != nullptr && context.storageImageMultisample && context.storageImageWriteWithoutFormat, "initial multisample samples require enabled formatless multisampled storage image writes");
|
||||
const auto format = TextureDetiler::ImageElementFormat(target.elementBytes);
|
||||
flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT | VK_IMAGE_CREATE_EXTENDED_USAGE_BIT;
|
||||
Require(storageSupports(elementFormat, VK_IMAGE_USAGE_STORAGE_BIT, 0u) && storageSupports(target.format, usage | VK_IMAGE_USAGE_STORAGE_BIT, flags), "the device cannot upload multisampled guest color through an unsigned storage view");
|
||||
Require(storageSupports(format, VK_IMAGE_USAGE_STORAGE_BIT, 0u) && storageSupports(target.format, usage | VK_IMAGE_USAGE_STORAGE_BIT, flags), "the device cannot initialize the multisampled color target through its unsigned storage view");
|
||||
usage |= VK_IMAGE_USAGE_STORAGE_BIT;
|
||||
upload = guestSampleUpload(target);
|
||||
if (!initialUpload) initialUpload = guestSampleUpload(this->context, target);
|
||||
}
|
||||
twinFormat = colorTwinFormat(context, target.format);
|
||||
if (twinFormat != VK_FORMAT_UNDEFINED) {
|
||||
@@ -254,37 +254,36 @@ public:
|
||||
memory = created.memory;
|
||||
view = created.view;
|
||||
allocationBytes = created.bytes;
|
||||
if (upload) {
|
||||
if (!initial.has_value()) {
|
||||
VkImageViewUsageCreateInfo storageUsage{VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO};
|
||||
storageUsage.usage = VK_IMAGE_USAGE_STORAGE_BIT;
|
||||
VkImageViewCreateInfo info{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
|
||||
info.pNext = &storageUsage;
|
||||
info.image = image;
|
||||
info.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
|
||||
info.format = elementFormat;
|
||||
info.format = TextureDetiler::ImageElementFormat(target.elementBytes);
|
||||
info.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
Check(context.Function<PFN_vkCreateImageView>("vkCreateImageView")(context.device, &info, nullptr, &uploadView), "vkCreateImageView multisampled guest color upload");
|
||||
Check(context.Function<PFN_vkCreateImageView>("vkCreateImageView")(context.device, &info, nullptr, &initialView), "vkCreateImageView initial multisample samples");
|
||||
}
|
||||
recordCommands(context, [&](VkCommandBuffer commands, Recorder* recorder) {
|
||||
if (!upload) {
|
||||
if (initial.has_value()) {
|
||||
recordToGeneral(commands, image);
|
||||
const VkImageSubresourceRange range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
context.Resolved(&DeviceFunctions::cmdClearColorImage, "vkCmdClearColorImage")(commands, image, VK_IMAGE_LAYOUT_GENERAL, &initial, 1, &range);
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT);
|
||||
context.Resolved(&DeviceFunctions::cmdClearColorImage, "vkCmdClearColorImage")(commands, image, VK_IMAGE_LAYOUT_GENERAL, &*initial, 1, &range);
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT);
|
||||
if (recorder != nullptr) Recorder::CountBarriers(Recorder::CommandClass::Draw, 2);
|
||||
return;
|
||||
}
|
||||
recordToGeneral(commands, image, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_SHADER_WRITE_BIT);
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT);
|
||||
const auto bytes = static_cast<std::uint64_t>(target.extent.width) * target.extent.height * target.samples * target.elementBytes;
|
||||
for (std::uint64_t offset = 0; offset < bytes; offset += stride) {
|
||||
const auto count = std::min<std::uint64_t>(stride, bytes - offset);
|
||||
context.detiler->DispatchMultisampleImage(commands, target.elementBytes, upload->Handle(), offset, (count + 3u) & ~std::uint64_t{3}, uploadView, target.extent.width, target.extent.height, 1u, target.samples, static_cast<std::uint32_t>(offset / target.elementBytes), static_cast<std::uint32_t>(count / target.elementBytes));
|
||||
}
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT);
|
||||
if (recorder != nullptr) {
|
||||
recorder->Keep(upload, upload->Bytes().size());
|
||||
Recorder::CountBarriers(Recorder::CommandClass::Draw, 3);
|
||||
} else {
|
||||
recordToGeneral(commands, image, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_SHADER_WRITE_BIT);
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT);
|
||||
const auto elements = static_cast<std::uint64_t>(target.extent.width) * target.extent.height * target.samples;
|
||||
const auto bytes = elements * target.elementBytes;
|
||||
for (std::uint64_t offset = 0; offset < bytes; offset += stride) {
|
||||
const auto count = std::min(stride, bytes - offset);
|
||||
context.detiler->DispatchMultisampleImage(commands, target.elementBytes, initialUpload->Handle(), offset, (count + 3u) & ~VkDeviceSize{3}, initialView, target.extent.width, target.extent.height, 1u, target.samples, static_cast<std::uint32_t>(offset / target.elementBytes), static_cast<std::uint32_t>(count / target.elementBytes));
|
||||
}
|
||||
if (recorder != nullptr) recorder->Keep(initialUpload, initialUpload->Bytes().size());
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT);
|
||||
if (recorder != nullptr) Recorder::CountBarriers(Recorder::CommandClass::Draw, 3);
|
||||
}
|
||||
});
|
||||
} catch (...) {
|
||||
@@ -431,6 +430,7 @@ public:
|
||||
checkDccCurrent(use, refilled);
|
||||
if (!refilled && !TakeMultisampledClear(cmask, colorFill, use, clear)) return;
|
||||
Keep();
|
||||
ensureGeneralLayout();
|
||||
recordCommands(context, [&](VkCommandBuffer commands, Recorder* recorder) {
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT);
|
||||
const VkImageSubresourceRange range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
@@ -455,6 +455,7 @@ public:
|
||||
fmaskCopy = {};
|
||||
metadataValid = std::make_shared<std::atomic<bool>>(true);
|
||||
const auto initial = initialDccClear(use);
|
||||
ensureGeneralLayout();
|
||||
cleared = initial;
|
||||
drawn = false;
|
||||
++contents;
|
||||
@@ -485,22 +486,21 @@ public:
|
||||
auto* recording = Recorder::Active();
|
||||
Require(recording != nullptr && recording->Device() == context.device, "a multisampled color target is only written by recorded dispatches on its device");
|
||||
Keep();
|
||||
ensureGeneralLayout();
|
||||
makeStorable(*recording);
|
||||
ApplyFastClear(target);
|
||||
const auto uintFormat = uintStorage ? uintStorageViewFormat(storageFormat) : VK_FORMAT_UNDEFINED;
|
||||
const bool useUintStorage = uintStorage && uintFormat != VK_FORMAT_UNDEFINED;
|
||||
auto& selectedView = useUintStorage ? uintStorageView : storageView;
|
||||
if (selectedView == VK_NULL_HANDLE) {
|
||||
VkImageViewUsageCreateInfo storageUsage{VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO};
|
||||
storageUsage.usage = VK_IMAGE_USAGE_STORAGE_BIT;
|
||||
VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
|
||||
viewInfo.pNext = &storageUsage;
|
||||
viewInfo.image = image;
|
||||
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
viewInfo.format = useUintStorage ? uintFormat : storageFormat;
|
||||
viewInfo.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
VkImageViewUsageCreateInfo viewUsage{VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO};
|
||||
if (useUintStorage) {
|
||||
viewUsage.usage = VK_IMAGE_USAGE_STORAGE_BIT;
|
||||
viewInfo.pNext = &viewUsage;
|
||||
}
|
||||
Check(context.Function<PFN_vkCreateImageView>("vkCreateImageView")(context.device, &viewInfo, nullptr, &selectedView), "vkCreateImageView multisampled target storage");
|
||||
}
|
||||
recordCommands(context, [&](VkCommandBuffer commands, Recorder* recorder) {
|
||||
@@ -541,6 +541,12 @@ public:
|
||||
note(target.cmaskAddress, target.cmaskBytes, cmaskFill);
|
||||
}
|
||||
|
||||
void PrepareForColorAttachment(const ColorTarget& use) {
|
||||
SettleStorageWrites();
|
||||
ensureGeneralLayout();
|
||||
ApplyFastClear(use);
|
||||
}
|
||||
|
||||
std::shared_ptr<Texture> Sampled(VkComponentMapping components, VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D) {
|
||||
Require(sampleable, "the device cannot sample the multisampled color target's format at its sample count");
|
||||
Require(cmask.Refusal().empty(), cmask.Refusal());
|
||||
@@ -555,9 +561,10 @@ public:
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT);
|
||||
Recorder::CountBarriers(Recorder::CommandClass::Draw, 1);
|
||||
});
|
||||
ensureSampledLayout();
|
||||
const std::array<std::uint32_t, 5> key{static_cast<std::uint32_t>(components.r), static_cast<std::uint32_t>(components.g), static_cast<std::uint32_t>(components.b), static_cast<std::uint32_t>(components.a), static_cast<std::uint32_t>(viewType)};
|
||||
auto& texture = textures[key];
|
||||
if (texture == nullptr) texture = std::make_shared<Texture>(context, image, target.format, VK_IMAGE_ASPECT_COLOR_BIT, components, viewType, 0, std::shared_ptr<OwnedImage>{}, orderFormat != VK_FORMAT_UNDEFINED ? VK_IMAGE_USAGE_SAMPLED_BIT : VkImageUsageFlags{0});
|
||||
if (texture == nullptr) texture = std::make_shared<Texture>(context, image, target.format, VK_IMAGE_ASPECT_COLOR_BIT, components, viewType, 0, std::shared_ptr<OwnedImage>{}, VK_IMAGE_USAGE_SAMPLED_BIT, VK_REMAINING_ARRAY_LAYERS, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
return {shared_from_this(), texture.get()};
|
||||
}
|
||||
|
||||
@@ -683,6 +690,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
VkImageLayout imageLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
bool sampleable = false;
|
||||
VkImageUsageFlags usage = 0;
|
||||
VkFormat storageFormat = VK_FORMAT_UNDEFINED;
|
||||
@@ -767,19 +775,45 @@ private:
|
||||
return true;
|
||||
}
|
||||
|
||||
VkImageView uploadView = VK_NULL_HANDLE;
|
||||
VkImageView initialView = VK_NULL_HANDLE;
|
||||
|
||||
std::shared_ptr<Buffer> guestSampleUpload(const ColorTarget& use) {
|
||||
std::optional<VkClearColorValue> initialColorContents(const Context& uploadContext, const ColorTarget& use, std::shared_ptr<Buffer>& initialUpload) {
|
||||
Require(use.cmaskAddress == 0, "initial multisample color samples with CMASK require a modeled metadata import");
|
||||
Require(use.layers == 1u && use.arraySlice == 0u && use.arrayLastSlice == 0u && use.depth == 1u && use.depthSlice == 0u && use.mipCount == 1u && use.mip == 0u && !use.mipTail, "initial multisample array, volume or mip contents are not implemented");
|
||||
Require(FindGuestColorTargetFormat(use.format, use.elementBytes).has_value(), "initial multisample color format or element size is unsupported");
|
||||
const auto bytes = MultisampledColorBytes(use);
|
||||
Require(bytes != 0 && bytes <= std::numeric_limits<std::size_t>::max(), "initial multisample color footprint is not implemented");
|
||||
std::vector<std::byte> initialColor(static_cast<std::size_t>(bytes));
|
||||
GuestMemory::Read(use.address, initialColor);
|
||||
const auto repeated = std::equal(initialColor.begin() + use.elementBytes, initialColor.end(), initialColor.begin());
|
||||
if (repeated) {
|
||||
std::array<std::uint32_t, 4> pattern{};
|
||||
auto texels = std::as_writable_bytes(std::span(pattern));
|
||||
for (std::size_t offset = 0; offset < texels.size(); offset += use.elementBytes) std::memcpy(texels.data() + offset, initialColor.data(), use.elementBytes);
|
||||
VkClearColorValue clear{};
|
||||
if (ClearColorForTexel(use.format, use.elementBytes, pattern, clear)) return clear;
|
||||
}
|
||||
Require(use.tileMode == ColorTileMode::RenderTarget, "nonuniform initial multisample color tiling is not implemented");
|
||||
initialUpload = guestSampleUpload(uploadContext, use, initialColor);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::shared_ptr<Buffer> guestSampleUpload(const Context& uploadContext, const ColorTarget& use, std::span<const std::byte> snapshot = {}) {
|
||||
Require(use.cmaskAddress == 0 && use.dccAddress == 0, "uploading multisampled guest color under compression metadata is not implemented");
|
||||
Require(use.layers == 1u && use.arraySlice == 0u && use.arrayLastSlice == 0u && use.depth == 1u && use.depthSlice == 0u && use.mipCount == 1u && use.mip == 0u && !use.mipTail, "uploading multisampled guest color of arrays, volumes or mips is not implemented");
|
||||
Require(use.tileMode == ColorTileMode::RenderTarget || use.tileMode == ColorTileMode::Z64KBX, "non-uniform multisampled guest color tiling is not implemented");
|
||||
const MultisampleTextureLayout layout(use.tileMode == ColorTileMode::Z64KBX ? TextureTileMode::kZ64KBX : TextureTileMode::kR64KBX, use.elementBytes, use.samples, use.extent.width, use.extent.height, 1u, use.pipeBankXor);
|
||||
Require(layout.Bytes() == MultisampledColorBytes(use) && layout.Bytes() <= std::numeric_limits<std::size_t>::max(), "the multisampled guest color layout and footprint differ");
|
||||
Require(layout.LinearBytes() / use.elementBytes <= std::numeric_limits<std::uint32_t>::max(), "the multisampled guest color element count is unsupported");
|
||||
std::vector<std::byte> guest(static_cast<std::size_t>(layout.Bytes()));
|
||||
GuestMemory::Read(use.address, guest);
|
||||
auto staging = std::make_shared<Buffer>(context, static_cast<std::size_t>((layout.LinearBytes() + 3u) & ~std::uint64_t{3}), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
|
||||
layout.Detile(guest, staging->Bytes().first(static_cast<std::size_t>(layout.LinearBytes())));
|
||||
std::vector<std::byte> guest;
|
||||
if (snapshot.empty()) {
|
||||
guest.resize(static_cast<std::size_t>(layout.Bytes()));
|
||||
GuestMemory::Read(use.address, guest);
|
||||
snapshot = guest;
|
||||
}
|
||||
Require(snapshot.size() == layout.Bytes(), "the multisampled guest color snapshot and footprint differ");
|
||||
auto staging = std::make_shared<Buffer>(uploadContext, static_cast<std::size_t>((layout.LinearBytes() + 3u) & ~std::uint64_t{3}), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
|
||||
layout.Detile(snapshot, staging->Bytes().first(static_cast<std::size_t>(layout.LinearBytes())));
|
||||
return staging;
|
||||
}
|
||||
|
||||
@@ -887,14 +921,40 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void transitionLayout(VkImageLayout next, VkAccessFlags sourceAccess, VkAccessFlags destinationAccess) {
|
||||
if (imageLayout == next) return;
|
||||
recordCommands(context, [&](VkCommandBuffer commands, Recorder* recorder) {
|
||||
VkImageMemoryBarrier barrier{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
||||
barrier.srcAccessMask = sourceAccess;
|
||||
barrier.dstAccessMask = destinationAccess;
|
||||
barrier.oldLayout = imageLayout;
|
||||
barrier.newLayout = next;
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.image = image;
|
||||
barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
context.Resolved(&DeviceFunctions::cmdPipelineBarrier, "vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
|
||||
if (recorder != nullptr) Recorder::CountBarriers(Recorder::CommandClass::Draw, 1);
|
||||
});
|
||||
imageLayout = next;
|
||||
}
|
||||
|
||||
void ensureGeneralLayout() {
|
||||
transitionLayout(VK_IMAGE_LAYOUT_GENERAL, VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT);
|
||||
}
|
||||
|
||||
void ensureSampledLayout() {
|
||||
transitionLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT);
|
||||
}
|
||||
|
||||
void release() noexcept {
|
||||
fmaskTextures.clear();
|
||||
textures.clear();
|
||||
retiredTextures.clear();
|
||||
firstSampleTextures.clear();
|
||||
firstSampleCopy.reset();
|
||||
if (initialView) context.Function<PFN_vkDestroyImageView>("vkDestroyImageView")(context.device, initialView, nullptr);
|
||||
for (const auto& [format, resolvePass] : resolvePasses) context.Function<PFN_vkDestroyRenderPass>("vkDestroyRenderPass")(context.device, resolvePass, nullptr);
|
||||
if (uploadView) context.Function<PFN_vkDestroyImageView>("vkDestroyImageView")(context.device, uploadView, nullptr);
|
||||
if (orderView) context.Function<PFN_vkDestroyImageView>("vkDestroyImageView")(context.device, orderView, nullptr);
|
||||
if (twinView) context.Function<PFN_vkDestroyImageView>("vkDestroyImageView")(context.device, twinView, nullptr);
|
||||
if (storageView) context.Function<PFN_vkDestroyImageView>("vkDestroyImageView")(context.device, storageView, nullptr);
|
||||
@@ -1076,7 +1136,7 @@ MultisampleTarget& acquire(const Context& context, const ColorTarget& requested)
|
||||
target->Recreate(use);
|
||||
target->use->hostOnly.store(true, std::memory_order_release);
|
||||
guard(context, use, target->active, target->metadataValid, target->use);
|
||||
target->ApplyFastClear(use);
|
||||
target->PrepareForColorAttachment(use);
|
||||
return *target;
|
||||
}
|
||||
{
|
||||
@@ -1097,20 +1157,20 @@ MultisampleTarget& acquire(const Context& context, const ColorTarget& requested)
|
||||
}
|
||||
target->cmask.CheckAddress(use.cmaskAddress);
|
||||
target->Keep();
|
||||
target->SettleStorageWrites();
|
||||
target->ApplyFastClear(use);
|
||||
target->PrepareForColorAttachment(use);
|
||||
collectFmaskWrites(target->target);
|
||||
stampUse(*target->use, target->target.address, guardedColorBytes(target->target));
|
||||
return *target;
|
||||
}
|
||||
const auto& use = requested;
|
||||
ClaimMultisampleTargetMemory(use.address, MultisampledColorBytes(use));
|
||||
auto target = std::make_shared<MultisampleTarget>(context, use);
|
||||
target->Keep();
|
||||
targets().push_back(std::move(target));
|
||||
auto& created = *targets().back();
|
||||
guard(context, use, created.active, created.metadataValid, created.use);
|
||||
if (use.cmaskFastClear && use.dccAddress == 0) created.cmask.SeedCode(CurrentUniformMetadataByte(use.cmaskAddress, static_cast<std::size_t>(use.cmaskBytes)));
|
||||
created.ApplyFastClear(use);
|
||||
created.PrepareForColorAttachment(use);
|
||||
created.Keep();
|
||||
return created;
|
||||
}
|
||||
@@ -1174,7 +1234,7 @@ MultisampleTarget& createFilled(const Context& context, const GuestTextureResour
|
||||
const bool uniform = htileCleared || std::equal(memory + sizeof(value), memory + bytes, memory);
|
||||
VkClearColorValue clear{};
|
||||
if (uniform && !MultisampledFillClearColor(use, value, clear)) refuse(std::string("whose color memory ") + range + " holds " + held + ", which has no exact clear value in its format");
|
||||
auto target = std::make_shared<MultisampleTarget>(context, use, !uniform);
|
||||
auto target = std::make_shared<MultisampleTarget>(context, use, uniform ? std::optional{clear} : std::nullopt, !uniform);
|
||||
target->Keep();
|
||||
targets().push_back(std::move(target));
|
||||
auto& created = *targets().back();
|
||||
|
||||
@@ -1180,9 +1180,9 @@ Texture::Texture(const Context& context, const std::shared_ptr<StorageTexture>&
|
||||
}
|
||||
}
|
||||
|
||||
Texture::Texture(const Context& context, VkImage depthImage, VkFormat depthFormat, VkImageAspectFlags aspect, VkComponentMapping components, VkImageViewType viewType, std::uint32_t baseLayer, std::shared_ptr<OwnedImage> owner, VkImageUsageFlags usage, std::uint32_t layerCount) : context(context), owned(std::move(owner)) {
|
||||
Texture::Texture(const Context& context, VkImage depthImage, VkFormat depthFormat, VkImageAspectFlags aspect, VkComponentMapping components, VkImageViewType viewType, std::uint32_t baseLayer, std::shared_ptr<OwnedImage> owner, VkImageUsageFlags usage, std::uint32_t layerCount, VkImageLayout imageLayout) : context(context), owned(std::move(owner)) {
|
||||
Require(owned == nullptr || (owned->image == depthImage && owned->context.device == context.device), "a depth sampled view must retain its own device and image");
|
||||
layout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
layout = imageLayout;
|
||||
VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
|
||||
VkImageViewUsageCreateInfo viewUsage{VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO};
|
||||
viewUsage.usage = usage;
|
||||
|
||||
@@ -11572,7 +11572,7 @@ void multisampleGuestAccessTests(const Device& device, Recorder& recorder) {
|
||||
std::cout << "4x color targets or nullDescriptor unavailable: multisampled target guest access not tested\n";
|
||||
return;
|
||||
}
|
||||
constexpr std::uint32_t side = 64;
|
||||
constexpr std::uint32_t side = 128;
|
||||
constexpr std::size_t targetBytes = 65536u;
|
||||
constexpr std::size_t bytes = 5u * 65536u;
|
||||
#ifdef _WIN32
|
||||
@@ -11631,6 +11631,7 @@ void multisampleGuestAccessTests(const Device& device, Recorder& recorder) {
|
||||
target.extent = {side, side};
|
||||
target.surfaceExtent = target.extent;
|
||||
target.format = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
target.tileMode = ColorTileMode::RenderTarget;
|
||||
target.bytes = targetBytes;
|
||||
target.samples = 4;
|
||||
const bool ordered = MayReorderSamples(context, target.samples) && SampleOrderStorageFormat(context, target.format, target.elementBytes, target.samples) != VK_FORMAT_UNDEFINED;
|
||||
@@ -11748,7 +11749,7 @@ void multisampleGuestAccessTests(const Device& device, Recorder& recorder) {
|
||||
ShaderResources arraySecond(context, sampledArrayCompute);
|
||||
ShaderResources combined(context, bothShapesCompute);
|
||||
Require(first.SampledTextures().size() == 1 && first.SampledTextures().front() != nullptr, "the multisampled target was not bound as a sampled texture");
|
||||
Require(first.SampledTextures().front()->Layout() == VK_IMAGE_LAYOUT_GENERAL && first.SampledTextures().front()->ViewFormat() == target.format, "the sampled multisampled target is not a view of the target's image in its layout");
|
||||
Require(first.SampledTextures().front()->Layout() == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL && first.SampledTextures().front()->ViewFormat() == target.format, "the sampled multisampled target is not a read-only view of the target's image");
|
||||
Require(first.SampledTextures().front()->SampledViewRange(false).type == VK_IMAGE_VIEW_TYPE_2D, "Image2DMultisample did not bind a 2D image view");
|
||||
Require(second.SampledTextures().front() == first.SampledTextures().front(), "a second lookup of the multisampled target made another texture");
|
||||
Require(arrayFirst.SampledTextures().size() == 1 && arrayFirst.SampledTextures().front() != nullptr, "the multisampled array target was not bound as a sampled texture");
|
||||
@@ -11761,8 +11762,8 @@ void multisampleGuestAccessTests(const Device& device, Recorder& recorder) {
|
||||
device.WaitQueue();
|
||||
recorder.Sync();
|
||||
}
|
||||
refusedSample(ShaderRecompiler::DescriptorKind::SampledImage, multisampledWords(side, 3u, address), "as a 64x64 texture of 8 samples", "sampling a 4x target as an 8x texture");
|
||||
refusedSample(ShaderRecompiler::DescriptorKind::SampledImage, multisampledWords(side / 2u, 2u, address), "as a 32x64 texture of 4 samples", "sampling a 4x target as a narrower texture");
|
||||
refusedSample(ShaderRecompiler::DescriptorKind::SampledImage, multisampledWords(side, 3u, address), "as a 128x128 texture of 8 samples", "sampling a 4x target as an 8x texture");
|
||||
refusedSample(ShaderRecompiler::DescriptorKind::SampledImage, multisampledWords(side / 2u, 2u, address), "as a 64x128 texture of 4 samples", "sampling a 4x target as a narrower texture");
|
||||
refusedSample(ShaderRecompiler::DescriptorKind::SampledImage, multisampledWords(side, 2u, address + 4u * 65536u), "which no draw rendered to and whose tile mode 0 has no modeled multisampled color footprint", "sampling a linear multisampled texture no draw rendered to");
|
||||
if (context.storageImageMultisample) {
|
||||
const auto stored = sampledProgram(ShaderRecompiler::DescriptorKind::StorageImage, multisampledWords(side, 2u, address));
|
||||
@@ -11778,10 +11779,11 @@ void multisampleGuestAccessTests(const Device& device, Recorder& recorder) {
|
||||
} else {
|
||||
refusedSample(ShaderRecompiler::DescriptorKind::StorageImage, multisampledWords(side, 2u, address), "as a storage image at its sample count", "storage access to a multisampled target without shaderStorageImageMultisample");
|
||||
}
|
||||
refused([&] { AgcDriver::GuestMemory::Read(address + 3u * 65536u + 256u, texels); }, "a CPU read", "a CPU read in the padding of a later sample plane");
|
||||
refused([&] { AgcDriver::GuestMemory::Read(address + 3u * 65536u + 256u, texels); }, "a CPU read", "a CPU read in the last block of the 4x target");
|
||||
AgcDriver::GuestMemory::Read(address + 4u * 65536u, texels);
|
||||
clearTargets();
|
||||
target.tileMode = ColorTileMode::RenderTarget;
|
||||
target.extent = {side / 2u, side / 2u};
|
||||
target.surfaceExtent = target.extent;
|
||||
{
|
||||
std::lock_guard gpu(GpuMutex());
|
||||
Require(MultisampleTargetView(context, target) != VK_NULL_HANDLE, "the tiled multisampled target has no view");
|
||||
@@ -11942,6 +11944,8 @@ void repeatStorageGuardTests(const Device& device, Recorder& recorder) {
|
||||
target.format = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
target.bytes = targetBytes;
|
||||
target.samples = 4;
|
||||
target.tileMode = ColorTileMode::RenderTarget;
|
||||
Require(MultisampledColorBytes(target) == targetBytes, "the guarding multisampled target has an unexpected footprint");
|
||||
std::lock_guard gpu(GpuMutex());
|
||||
Require(MultisampleTargetView(context, target) != VK_NULL_HANDLE, "the guarding multisampled target has no view");
|
||||
recorder.Submit();
|
||||
|
||||
@@ -2,8 +2,9 @@ target_include_directories(agc_driver_alpha_to_coverage_tests PRIVATE $<TARGET_P
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test(NAME agc_driver_dithered_alpha_to_coverage COMMAND agc_driver_alpha_to_coverage_tests --dithered8)
|
||||
add_test(NAME agc_driver_msaa_dcc_recreate_layout COMMAND agc_driver_alpha_to_coverage_tests --dcc-recreate-layout)
|
||||
add_test(NAME agc_driver_two_sample_dithered_alpha_to_coverage COMMAND agc_driver_alpha_to_coverage_tests --dithered2)
|
||||
set(multisampledDccClearTests)
|
||||
set(multisampledDccClearTests agc_driver_msaa_dcc_recreate_layout)
|
||||
foreach(samples IN ITEMS 2 4 8)
|
||||
set(testName "agc_driver_msaa_dcc_srgb_clear_${samples}")
|
||||
add_test(NAME ${testName} COMMAND agc_driver_alpha_to_coverage_tests --dcc-srgb-clear ${samples})
|
||||
|
||||
+6
@@ -13,4 +13,10 @@ if(BUILD_TESTING)
|
||||
set_tests_properties(agc_driver_native_msaa_vertex_input PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test(NAME agc_driver_native_color_initial COMMAND agc_driver_sample_multisample_target_tests --initial)
|
||||
add_test(NAME agc_driver_native_color_initial_lifetime COMMAND agc_driver_sample_multisample_target_tests --initial-lifetime)
|
||||
set_tests_properties(agc_driver_native_color_initial agc_driver_native_color_initial_lifetime PROPERTIES TIMEOUT 30 ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_driver_native_color_initial agc_driver_native_color_initial_lifetime PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
endif()
|
||||
@@ -309,7 +309,7 @@ struct Scene {
|
||||
Pages pixels{TargetLayout.Bytes()};
|
||||
Pages vertices{BlockBytes};
|
||||
Pages code{BlockBytes};
|
||||
Pages constants{BlockBytes};
|
||||
Pages constants{BlockBytes * 2};
|
||||
Pages spare{BlockBytes};
|
||||
Pages ring{BlockBytes};
|
||||
bool cachedVertex = false;
|
||||
|
||||
@@ -424,6 +424,98 @@ void DitheredCoverage(AgcDriver::VulkanDevice& device, std::uint32_t samples, st
|
||||
AgcDriver::Graphics::ClearMultisampleTargets(device.Device());
|
||||
}
|
||||
|
||||
void CheckDccSamples(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarget& target, const Color& written, float untouched) {
|
||||
alignas(256) static constexpr std::array<std::uint32_t, 26> code{
|
||||
0x34020087, 0xe0301000, 0x80000201, 0xe0301004, 0x80000301, 0xe0301008, 0x80000401, 0xe030100c,
|
||||
0x80000501, 0xe0301010, 0x80000601, 0xe0301014, 0x80000701, 0xbf8c3f70, 0xf0001f30, 0x00011005,
|
||||
0xbf8c3f70, 0xe0701030, 0x80001001, 0xe0701034, 0x80001101, 0xe0701038, 0x80001201, 0xe070103c,
|
||||
0x80001301, 0xbf810000,
|
||||
};
|
||||
alignas(256) static std::array<std::uint32_t, 32u * 32u> buffer{};
|
||||
buffer.fill(0xdeadbeefu);
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(buffer.data());
|
||||
const auto imageAddress = target.address | target.pipeBankXor;
|
||||
const auto sampleShift = static_cast<std::uint32_t>(std::countr_zero(target.samples));
|
||||
const std::array<std::uint32_t, 4> descriptor{static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu), static_cast<std::uint32_t>(sizeof(buffer)), 0x31016facu};
|
||||
const std::array<std::uint32_t, 8> image{
|
||||
static_cast<std::uint32_t>(imageAddress >> 8u),
|
||||
static_cast<std::uint32_t>((imageAddress >> 40u) & 0xffu) | (71u << 20u) | (((Width - 1u) & 3u) << 30u),
|
||||
((Width - 1u) >> 2u) | ((Height - 1u) << 14u),
|
||||
0xfacu | (sampleShift << 16u) | (0x1bu << 20u) | (14u << 28u), 0u, sampleShift << 4u, 0u, 0u,
|
||||
};
|
||||
std::vector<std::uint32_t> userData(16u);
|
||||
std::copy(descriptor.begin(), descriptor.end(), userData.begin());
|
||||
std::copy(image.begin(), image.end(), userData.begin() + 4u);
|
||||
for (std::uint32_t tid = 0; tid < 32u; ++tid) {
|
||||
const std::array<std::uint32_t, 6> coordinates{0u, 0u, 0u, (tid / target.samples) % 2u == 0u ? 0u : ScissorWidth, 0u, tid % target.samples};
|
||||
std::copy(coordinates.begin(), coordinates.end(), buffer.begin() + tid * 32u);
|
||||
}
|
||||
const auto codeAddress = reinterpret_cast<std::uintptr_t>(code.data());
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{codeAddress, std::as_bytes(std::span(code))}}};
|
||||
const ShaderRecompiler::ShaderComputeStageInfo compute{{32u, 1u, 1u}, 0u, {false, false, false}, false, 1};
|
||||
ShaderRecompiler::RecompileRequest request{
|
||||
{ShaderStage::Compute, codeAddress, code, 0, {}},
|
||||
{32, 0, userData, compute, std::nullopt, std::nullopt, memory},
|
||||
device.Target(),
|
||||
{0, 0, 0, 128}
|
||||
};
|
||||
request.useCache = false;
|
||||
device.Dispatch(ShaderRecompiler::Recompile(request), 1u, 1u, 1u, {}, codeAddress);
|
||||
device.WaitIdle();
|
||||
for (std::uint32_t tid = 0; tid < 32u; ++tid) {
|
||||
for (std::uint32_t channel = 0; channel < 4u; ++channel) {
|
||||
const auto expected = (tid / target.samples) % 2u == 0u && tid % target.samples == 0u ? written[channel] : untouched;
|
||||
Require(std::bit_cast<float>(buffer[tid * 32u + 12u + channel]) == expected, "a sampled DCC target did not preserve its drawn and untouched samples across recreation");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DccRecreateLayoutTests(AgcDriver::VulkanDevice& device) {
|
||||
const ColorTargetLayout layout(Width, Height, ColorTileMode::RenderTarget, 8);
|
||||
Block source(layout.Bytes() * 4u);
|
||||
Block metadata(BlockBytes);
|
||||
Block resolved(layout.Bytes());
|
||||
struct ClearTargets {
|
||||
AgcDriver::VulkanDevice& device;
|
||||
~ClearTargets() {
|
||||
std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex());
|
||||
device.SubmitRecorded(true);
|
||||
device.WaitIdle();
|
||||
AgcDriver::Graphics::ClearMultisampleTargets(device.Device());
|
||||
AgcDriver::Graphics::ClearCachedTextures(device.Device());
|
||||
}
|
||||
} clearTargets{device};
|
||||
std::memset(source.data, 0, source.bytes);
|
||||
std::memset(metadata.data, 0, metadata.bytes);
|
||||
std::memset(resolved.data, 0, resolved.bytes);
|
||||
auto target = DecodeTarget(source, 4u);
|
||||
target.dccAddress = metadata.Address();
|
||||
target.dccPipeAligned = true;
|
||||
const Color written{0.25f, 0.25f, 0.25f, 0.25f};
|
||||
Draw(device, target, false, Float32Export, Program(written, Float32Export, 1u), false, true);
|
||||
CheckDccSamples(device, target, written, 0.0f);
|
||||
const std::vector<std::byte> ones(AgcDriver::Graphics::MultisampledDccBytes(target), std::byte{0xc0});
|
||||
AgcDriver::GuestMemory::Write(metadata.Address(), ones);
|
||||
target.pipeBankXor = 0x4000u;
|
||||
Draw(device, target, false, Float32Export, Program(written, Float32Export, 1u), false, true);
|
||||
CheckDccSamples(device, target, written, 1.0f);
|
||||
AgcDriver::Graphics::ColorMetadataPass pass{};
|
||||
pass.mode = AgcDriver::Graphics::ColorMetadataPass::Mode::Resolve;
|
||||
pass.source = target;
|
||||
pass.targets = {DecodeTarget(resolved)};
|
||||
device.ColorMetadataPass(pass);
|
||||
const auto stored = ReadBack(device, resolved);
|
||||
for (std::uint32_t y = 0; y < Height; ++y) {
|
||||
for (std::uint32_t x = 0; x < Width; ++x) {
|
||||
for (std::uint32_t channel = 0; channel < 4u; ++channel) {
|
||||
const auto expected = x < ScissorWidth ? 1.0f + (written[channel] - 1.0f) / 4.0f : 1.0f;
|
||||
Require(FromHalf(stored[layout.Offset(x, y) / 2u + channel]) == expected, "a sampled DCC target recreated with another pipe-bank XOR did not resolve its new samples");
|
||||
}
|
||||
}
|
||||
}
|
||||
programCount = 0;
|
||||
}
|
||||
|
||||
constexpr std::array<DitherCase, 36> EightSampleDitherCases{{
|
||||
{0x00000000u, 9u, {0x00u, 0x00u, 0x00u, 0x00u}},
|
||||
{0x80000000u, 9u, {0x00u, 0x00u, 0x00u, 0x00u}},
|
||||
@@ -1269,6 +1361,11 @@ int main(int argc, char** argv) {
|
||||
try {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--dcc-recreate-layout") {
|
||||
DccRecreateLayoutTests(*device);
|
||||
std::puts("sampled multisample DCC target recreation and read-back passed");
|
||||
return 0;
|
||||
}
|
||||
if (argc == 3 && std::string_view(argv[1]) == "--dcc-srgb-clear") {
|
||||
const auto samples = std::string_view(argv[2]) == "2" ? 2u : std::string_view(argv[2]) == "4" ? 4u : std::string_view(argv[2]) == "8" ? 8u : 0u;
|
||||
Require(samples != 0, "unknown sRGB DCC test sample count");
|
||||
|
||||
@@ -331,7 +331,8 @@ void MultisampledCase(AgcDriver::VulkanDevice& device, const Shaders& compiled,
|
||||
}};
|
||||
AgcDriver::Graphics::State state{};
|
||||
state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, 32u, 32u, std::nullopt, std::nullopt};
|
||||
state.color = {reinterpret_cast<std::uintptr_t>(block + MultisampledColorOffset), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, Width * Height * 16u, 0xe4u};
|
||||
state.color = {reinterpret_cast<std::uintptr_t>(block + MultisampledColorOffset), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, 1u << 16u, 0xe4u};
|
||||
state.color.tileMode = AgcDriver::Graphics::ColorTileMode::RenderTarget;
|
||||
state.color.samples = 4;
|
||||
state.colors = {state.color};
|
||||
state.hasColorTarget = true;
|
||||
|
||||
@@ -26,6 +26,9 @@
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
namespace AgcDriver {
|
||||
@@ -61,6 +64,7 @@ public:
|
||||
GuestBlock& operator=(const GuestBlock&) = delete;
|
||||
|
||||
std::uint64_t Address() const { return static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(block)); }
|
||||
std::span<std::byte> Bytes() const { return {block, BlockBytes}; }
|
||||
|
||||
private:
|
||||
std::byte* block = nullptr;
|
||||
@@ -219,9 +223,53 @@ bool TwinFormatsShareTheirSamples(AgcDriver::VulkanDevice& device) {
|
||||
FillTakenThroughTwin(device, context, VK_FORMAT_R8G8B8A8_SRGB, VK_FORMAT_R8G8B8A8_UNORM, 0xff0000ffu);
|
||||
}
|
||||
auto& other = NewBlock();
|
||||
const auto unorm = Acquire(device, context, MultisampledTarget(other, VK_FORMAT_R8G8B8A8_UNORM));
|
||||
const auto floating = Acquire(device, context, MultisampledTarget(other, VK_FORMAT_R16G16_SFLOAT));
|
||||
Require(floating.image != unorm.image, "a multisampled surface used in an unrelated format of the same size shares the other format's samples");
|
||||
constexpr std::uint32_t guestPattern = 0x3c003800u;
|
||||
constexpr std::uint32_t hostPattern = 0xff0000ffu;
|
||||
for (std::size_t offset = 0; offset < other.Bytes().size(); offset += sizeof(guestPattern)) std::memcpy(other.Bytes().data() + offset, &guestPattern, sizeof(guestPattern));
|
||||
const std::vector<std::byte> before(other.Bytes().begin(), other.Bytes().end());
|
||||
const auto unormTarget = MultisampledTarget(other, VK_FORMAT_R8G8B8A8_UNORM);
|
||||
const auto floatTarget = MultisampledTarget(other, VK_FORMAT_R16G16_SFLOAT);
|
||||
const auto unorm = Acquire(device, context, unormTarget);
|
||||
const auto colorBytes = static_cast<std::size_t>(AgcDriver::Graphics::MultisampledColorBytes(unormTarget));
|
||||
Require(AgcDriver::Graphics::MultisampledColorFillAbsorbs(other.Address(), colorBytes, hostPattern), "the unrelated-format reuse control did not retain its fill on the host");
|
||||
AgcDriver::Graphics::NoteColorMetadataFill(other.Address(), colorBytes, hostPattern);
|
||||
const auto filled = Acquire(device, context, unormTarget);
|
||||
Require(filled.image == unorm.image && filled.owner == unorm.owner, "the host fill replaced its resident owner");
|
||||
{
|
||||
std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex());
|
||||
Require(UniformTexel(context, filled.image, unormTarget.format) == Bytes(hostPattern), "the old owner did not retain its distinct host samples");
|
||||
}
|
||||
const auto floating = Acquire(device, context, floatTarget);
|
||||
Require(floating.image != unorm.image && floating.owner != unorm.owner, "an unrelated-format bind reused the previous owner's samples");
|
||||
const auto floatAgain = Acquire(device, context, floatTarget);
|
||||
Require(floatAgain.image == floating.image && floatAgain.view == floating.view && floatAgain.owner == floating.owner, "the current floating-point owner was not retained");
|
||||
{
|
||||
std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex());
|
||||
Require(UniformTexel(context, floating.image, floatTarget.format) == Bytes(guestPattern), "the replacement did not initialize its samples from the original guest bytes");
|
||||
Require(UniformTexel(context, unorm.image, unormTarget.format) == Bytes(hostPattern), "the retained old allocation's samples changed during replacement");
|
||||
}
|
||||
const auto replacedUnorm = Acquire(device, context, unormTarget);
|
||||
Require(replacedUnorm.image != unorm.image && replacedUnorm.owner != unorm.owner, "an unrelated-format bind left its previous owner resident");
|
||||
Require(replacedUnorm.image != floating.image && replacedUnorm.owner != floating.owner, "returning to UNORM reused the floating-point owner's samples");
|
||||
{
|
||||
std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex());
|
||||
Require(UniformTexel(context, replacedUnorm.image, unormTarget.format) == Bytes(guestPattern), "returning to UNORM did not initialize from the original guest bytes");
|
||||
}
|
||||
const auto replacedFloat = Acquire(device, context, floatTarget);
|
||||
Require(replacedFloat.image != floating.image && replacedFloat.owner != floating.owner, "returning to floating point found its retired owner");
|
||||
{
|
||||
std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex());
|
||||
Require(UniformTexel(context, replacedFloat.image, floatTarget.format) == Bytes(guestPattern), "the second floating-point owner lost the initial guest samples");
|
||||
}
|
||||
Require(std::equal(before.begin(), before.end(), other.Bytes().begin()), "unrelated-format replacement changed guest bytes or padding");
|
||||
std::string refusal;
|
||||
try {
|
||||
AgcDriver::Graphics::RequireNoMultisampleTarget(other.Address(), colorBytes, "a read after unrelated-format replacement");
|
||||
} catch (const std::runtime_error& error) {
|
||||
refusal = error.what();
|
||||
}
|
||||
Require(refusal.find("overlaps multisampled color target") != std::string::npos, "the replacement's host-only samples are not guarded");
|
||||
std::cout << "unrelated-format binding retires old owners, preserves guest bytes and initializes distinct guarded samples\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ using ShaderRecompiler::ShaderStage;
|
||||
|
||||
constexpr std::uint32_t Samples = 4;
|
||||
|
||||
alignas(4096) std::array<std::byte, Width * Height * 4 * Samples> Multisampled{};
|
||||
alignas(4096) std::array<std::byte, 65536> Multisampled{};
|
||||
alignas(4096) std::array<float, Width * Height> DepthA{};
|
||||
alignas(4096) std::array<float, Width * Height> DepthB{};
|
||||
|
||||
@@ -87,6 +87,7 @@ void Draw(const Context& context, AgcDriver::VulkanDevice& device, const Shaders
|
||||
State state{};
|
||||
state.stages = {ShaderPath::Vertex, 0u, 32u, 32u, std::nullopt, std::nullopt};
|
||||
state.color = {reinterpret_cast<std::uintptr_t>(Multisampled.data()), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, Multisampled.size(), 0xe4u};
|
||||
state.color.tileMode = ColorTileMode::RenderTarget;
|
||||
state.color.samples = Samples;
|
||||
state.colors = {state.color};
|
||||
state.hasColorTarget = true;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp"
|
||||
#include "DepthFastClearHarness.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
@@ -9,6 +11,7 @@
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
@@ -34,11 +37,29 @@ using ShaderRecompiler::ShaderStage;
|
||||
|
||||
constexpr std::uint32_t Samples = 4;
|
||||
|
||||
alignas(4096) std::array<std::byte, Width * Height * 4 * Samples> ColorA{};
|
||||
alignas(4096) std::array<std::byte, Width * Height * 4 * Samples> ColorB{};
|
||||
alignas(4096) std::array<float, Width * Height> DepthA{};
|
||||
alignas(4096) std::array<float, Width * Height> DepthB{};
|
||||
alignas(4096) std::array<float, Width * Height> DepthC{};
|
||||
constexpr std::size_t SurfaceBytes = 65536;
|
||||
|
||||
class Surface {
|
||||
public:
|
||||
std::span<std::byte> Bytes() {
|
||||
void* pointer = storage.data();
|
||||
std::size_t space = storage.size();
|
||||
auto* aligned = static_cast<std::byte*>(std::align(SurfaceBytes, SurfaceBytes, pointer, space));
|
||||
Require(aligned != nullptr, "pass break reasons: cannot align the surface backing");
|
||||
return {aligned, SurfaceBytes};
|
||||
}
|
||||
|
||||
std::uint64_t Address() { return reinterpret_cast<std::uintptr_t>(Bytes().data()); }
|
||||
|
||||
private:
|
||||
std::array<std::byte, SurfaceBytes * 2> storage{};
|
||||
};
|
||||
|
||||
Surface ColorA{};
|
||||
Surface ColorB{};
|
||||
Surface DepthA{};
|
||||
Surface DepthB{};
|
||||
Surface DepthC{};
|
||||
|
||||
struct Counts {
|
||||
DrawPassCounts passes;
|
||||
@@ -69,16 +90,19 @@ void ExpectBreak(const Counts& before, const Counts& after, PassBreak reason, co
|
||||
}
|
||||
}
|
||||
|
||||
void Draw(const Context& context, AgcDriver::VulkanDevice& device, const Shaders& compiled, std::span<std::byte> color, std::uint64_t depth) {
|
||||
void Draw(const Context& context, AgcDriver::VulkanDevice& device, const Shaders& compiled, Surface& color, std::uint64_t depth) {
|
||||
const std::array<CompiledShader, 2> shaders{{
|
||||
{ShaderStage::Vertex, &compiled.vertex, 0},
|
||||
{ShaderStage::Fragment, &compiled.pixel, PixelPushOffset(compiled.vertexPush, device.Target())}
|
||||
}};
|
||||
State state{};
|
||||
state.stages = {ShaderPath::Vertex, 0u, 32u, 32u, std::nullopt, std::nullopt};
|
||||
state.color = {reinterpret_cast<std::uintptr_t>(color.data()), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, color.size(), 0xe4u};
|
||||
state.color = {color.Address(), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, color.Bytes().size(), 0xe4u};
|
||||
state.color.surfaceAddress = state.color.address;
|
||||
state.color.surfaceExtent = state.color.extent;
|
||||
state.color.tileMode = ColorTileMode::RenderTarget;
|
||||
state.color.samples = Samples;
|
||||
Expect(MultisampledColorBytes(state.color) == color.Bytes().size(), "the color backing differs from its multisample footprint");
|
||||
state.colors = {state.color};
|
||||
state.hasColorTarget = true;
|
||||
state.depth = DepthTarget{depth, 0, {Width, Height}, VK_FORMAT_D32_SFLOAT, 1.0f, 0, 0, false, Samples};
|
||||
@@ -121,9 +145,10 @@ int main() {
|
||||
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
|
||||
Recorder recorder(context);
|
||||
recorder.Activate();
|
||||
const auto depthA = reinterpret_cast<std::uintptr_t>(DepthA.data());
|
||||
const auto depthB = reinterpret_cast<std::uintptr_t>(DepthB.data());
|
||||
const auto depthC = reinterpret_cast<std::uintptr_t>(DepthC.data());
|
||||
Expect(MultisampledDepthBytes_nid_no_patch({Width, Height}, sizeof(float), Samples) == DepthA.Bytes().size(), "the depth backing differs from its multisample footprint");
|
||||
const auto depthA = DepthA.Address();
|
||||
const auto depthB = DepthB.Address();
|
||||
const auto depthC = DepthC.Address();
|
||||
auto before = Snapshot();
|
||||
Draw(context, *device, shaders, ColorA, depthA);
|
||||
auto after = Snapshot();
|
||||
|
||||
@@ -3,11 +3,14 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/BufferPool.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/MultisampleTextureLayout.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Resources.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/State.hpp"
|
||||
#include "prx/libc/include/GuestAllocations.hpp"
|
||||
@@ -166,7 +169,7 @@ alignas(256) constexpr std::array<std::uint32_t, 17> BdaSpanCode{
|
||||
};
|
||||
|
||||
constexpr std::array<std::array<float, 4>, 3> Triangle{{
|
||||
{-1.0f, -1.0f, 0.5f, 1.0f}, {0.83f, -1.0f, 0.5f, 1.0f}, {-1.0f, 0.71f, 0.5f, 1.0f}
|
||||
{-1.0f, -1.0f, 0.5f, 1.0f}, {0.83f, -1.0f, 0.5f, 1.0f}, {-1.0f, 0.31f, 0.5f, 1.0f}
|
||||
}};
|
||||
|
||||
alignas(256) std::array<std::uint32_t, Threads * Words> Buffer{};
|
||||
@@ -282,8 +285,10 @@ AgcDriver::Graphics::ColorTarget DecodeTarget(std::uint64_t address, std::uint32
|
||||
return color;
|
||||
}
|
||||
|
||||
AgcDriver::Graphics::ColorTarget DecodeTarget(const Block& block) {
|
||||
return DecodeTarget(block.Address());
|
||||
AgcDriver::Graphics::ColorTarget DecodeTarget(const Block& block, std::uint32_t pipeBankXor = 0) {
|
||||
const auto color = DecodeTarget(block.Address() | pipeBankXor);
|
||||
Require(color.address == block.Address() && color.pipeBankXor == pipeBankXor, "the color target lost its base address or pipe/bank XOR");
|
||||
return color;
|
||||
}
|
||||
|
||||
AgcDriver::Graphics::ColorTarget DecodeSwappedTarget(const Block& block) {
|
||||
@@ -439,8 +444,8 @@ void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::State& sta
|
||||
device.Draw(state, draw, shaders);
|
||||
}
|
||||
|
||||
std::array<std::uint32_t, 8> TargetDescriptor(const Block& block, std::uint32_t sampleShift, std::uint32_t format = Format16161616Float, std::uint32_t tileMode = TileRenderTarget) {
|
||||
const auto address = block.Address();
|
||||
std::array<std::uint32_t, 8> TargetDescriptor(const Block& block, std::uint32_t sampleShift, std::uint32_t pipeBankXor = 0, std::uint32_t format = Format16161616Float, std::uint32_t tileMode = TileRenderTarget) {
|
||||
const auto address = block.Address() | pipeBankXor;
|
||||
return {
|
||||
static_cast<std::uint32_t>(address >> 8u),
|
||||
static_cast<std::uint32_t>((address >> 40u) & 0xffu) | (format << 20u) | (((Side - 1u) & 3u) << 30u),
|
||||
@@ -561,8 +566,8 @@ std::vector<std::array<float, 4>> LoadSamples(AgcDriver::VulkanDevice& device, c
|
||||
return loaded;
|
||||
}
|
||||
|
||||
std::vector<std::array<float, 4>> LoadSamples(AgcDriver::VulkanDevice& device, const Block& block, std::uint32_t format = Format16161616Float, std::uint32_t tileMode = TileRenderTarget) {
|
||||
return LoadSamples(device, TargetDescriptor(block, SampleShift, format, tileMode));
|
||||
std::vector<std::array<float, 4>> LoadSamples(AgcDriver::VulkanDevice& device, const Block& block, std::uint32_t fragments = Samples, std::uint32_t pipeBankXor = 0, std::uint32_t format = Format16161616Float, std::uint32_t tileMode = TileRenderTarget) {
|
||||
return LoadSamples(device, TargetDescriptor(block, static_cast<std::uint32_t>(std::countr_zero(fragments)), pipeBankXor, format, tileMode), fragments);
|
||||
}
|
||||
|
||||
void CheckFirstSampleReads(AgcDriver::VulkanDevice& device, const std::array<std::uint32_t, 8>& texture, const std::vector<std::array<float, 4>>& samples, bool distinct, const std::string& what, const std::array<std::uint32_t, 44>& code = PlaneCode, const char* second = "image_sample_lz") {
|
||||
@@ -598,7 +603,7 @@ void CheckFirstSampleReads(AgcDriver::VulkanDevice& device, const std::array<std
|
||||
}
|
||||
|
||||
std::array<std::uint32_t, 8> CompressedDescriptor(const Block& block, std::uint32_t tileMode, std::uint64_t htile) {
|
||||
auto texture = TargetDescriptor(block, SampleShift, Format16161616Float, tileMode);
|
||||
auto texture = TargetDescriptor(block, SampleShift, 0u, Format16161616Float, tileMode);
|
||||
const auto meta = htile >> 8u;
|
||||
texture[6] = (1u << 20u) | (1u << 21u) | (static_cast<std::uint32_t>(meta & 0xffu) << 24u);
|
||||
texture[7] = static_cast<std::uint32_t>(meta >> 8u);
|
||||
@@ -617,21 +622,24 @@ double EdgeDistance(double x, double y, std::size_t from) {
|
||||
return side(c[0], c[1]) < 0 ? -side(x, y) : side(x, y);
|
||||
}
|
||||
|
||||
void CheckSamples(const AgcDriver::Graphics::State& state, const std::vector<std::array<float, 4>>& loaded, const std::array<float, 4>& background) {
|
||||
void CheckSamples(const AgcDriver::Graphics::State& state, const std::vector<std::array<float, 4>>& loaded, const std::array<float, 4>& background, std::span<const std::array<float, 4>> supplied = {}) {
|
||||
const auto locations = AgcDriver::Graphics::SampleLocations(state);
|
||||
std::array<std::uint32_t, Samples + 1u> pixelsByCoverage{};
|
||||
std::array<std::uint32_t, Samples> coveredBySample{};
|
||||
Require(loaded.size() == Side * Side * state.samples, "the multisample result has the wrong element count");
|
||||
Require(supplied.empty() || supplied.size() == loaded.size(), "the initial sample count does not match the rendered target");
|
||||
std::vector<std::uint32_t> pixelsByCoverage(state.samples + 1u);
|
||||
std::vector<std::uint32_t> coveredBySample(state.samples);
|
||||
std::uint32_t checked = 0;
|
||||
for (std::uint32_t y = 0; y < Side; ++y) {
|
||||
for (std::uint32_t x = 0; x < Side; ++x) {
|
||||
std::uint32_t covered = 0;
|
||||
for (std::uint32_t sample = 0; sample < Samples; ++sample) {
|
||||
const auto& texel = loaded[(y * Side + x) * Samples + sample];
|
||||
for (std::uint32_t sample = 0; sample < state.samples; ++sample) {
|
||||
const auto index = (y * Side + x) * state.samples + sample;
|
||||
const auto& texel = loaded[index];
|
||||
const bool drawn = texel == Drawn;
|
||||
const bool empty = texel == background;
|
||||
const bool empty = texel == (supplied.empty() ? background : supplied[index]);
|
||||
char where[160];
|
||||
std::snprintf(where, sizeof(where), "sample %u of pixel (%u, %u) loaded (%g, %g, %g, %g)", sample, x, y, texel[0], texel[1], texel[2], texel[3]);
|
||||
Require(drawn || empty, std::string(where) + ", neither the drawn color nor the cleared target");
|
||||
Require(drawn || empty, std::string(where) + ", neither the drawn color nor the initial target sample");
|
||||
covered += drawn ? 1u : 0u;
|
||||
coveredBySample[sample] += drawn ? 1u : 0u;
|
||||
const auto px = x + static_cast<double>(locations[sample].x);
|
||||
@@ -644,9 +652,9 @@ void CheckSamples(const AgcDriver::Graphics::State& state, const std::vector<std
|
||||
++pixelsByCoverage[covered];
|
||||
}
|
||||
}
|
||||
Require(checked >= Side * Side * Samples * 9u / 10u, "fewer than nine in ten samples are clear of the triangle's edges");
|
||||
Require(pixelsByCoverage[0] != 0 && pixelsByCoverage[Samples] != 0, "the triangle covers every pixel or none");
|
||||
Require(pixelsByCoverage[1] + pixelsByCoverage[2] + pixelsByCoverage[3] != 0, "no pixel is partly covered, so the samples of a pixel were not told apart");
|
||||
Require(checked >= Side * Side * state.samples * 9u / 10u, "fewer than nine in ten samples are clear of the triangle's edges");
|
||||
Require(pixelsByCoverage[0] != 0 && pixelsByCoverage[state.samples] != 0, "the triangle covers every pixel or none");
|
||||
Require(std::any_of(pixelsByCoverage.begin() + 1, pixelsByCoverage.end() - 1, [](auto count) { return count != 0; }), "no pixel is partly covered, so the samples of a pixel were not told apart");
|
||||
Require(std::adjacent_find(coveredBySample.begin(), coveredBySample.end(), std::not_equal_to<>()) != coveredBySample.end(), "every sample index has the same coverage, so the sample index was not told apart");
|
||||
}
|
||||
|
||||
@@ -953,6 +961,189 @@ void RequireRefused(AgcDriver::VulkanDevice& device, const std::array<std::uint3
|
||||
Require(refusal.find(expected) != std::string::npos, what + " was not refused: " + refusal);
|
||||
}
|
||||
|
||||
void RequireInitialRefused(const AgcDriver::Graphics::Context& context, const AgcDriver::Graphics::ColorTarget& color, const std::string& expected) {
|
||||
std::string refusal;
|
||||
try {
|
||||
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
|
||||
AgcDriver::Graphics::MultisampleTargetView(context, color);
|
||||
} catch (const std::exception& error) {
|
||||
refusal = error.what();
|
||||
}
|
||||
Require(refusal.find(expected) != std::string::npos, "native color initialization was not refused: " + refusal);
|
||||
}
|
||||
|
||||
bool InitialCountSupported(const AgcDriver::Graphics::Context& context, VkFormat format, std::uint32_t samples) {
|
||||
VkImageFormatProperties supported{};
|
||||
const auto result = context.imageFormatProperties(context.physical, format, VK_IMAGE_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, 0u, &supported);
|
||||
if (result != VK_ERROR_FORMAT_NOT_SUPPORTED) AgcDriver::Graphics::Check(result, "native color initialization fixture format query");
|
||||
if (result == VK_SUCCESS && (supported.sampleCounts & samples) != 0 && (context.limits.framebufferColorSampleCounts & samples) != 0) return true;
|
||||
std::printf("skipped, the device lacks native color initialization format=%d samples=%u\n", static_cast<int>(format), samples);
|
||||
return false;
|
||||
}
|
||||
|
||||
PFN_vkGetPhysicalDeviceImageFormatProperties imageFormatProperties = nullptr;
|
||||
|
||||
std::vector<std::array<float, 4>> StoreInitial(const Block& block, std::uint32_t fragments, std::uint32_t pipeBankXor, bool uniform) {
|
||||
using namespace AgcDriver::Graphics;
|
||||
const MultisampleTextureLayout layout(TextureTileMode::kR64KBX, 8u, fragments, Side, Side, 1u, pipeBankXor);
|
||||
Require(layout.Bytes() <= block.bytes, "the initial color footprint exceeds its allocation");
|
||||
StoreWords(block, block.bytes, 0x38003800u);
|
||||
std::vector<std::byte> linear(static_cast<std::size_t>(layout.LinearBytes()));
|
||||
std::vector<std::array<float, 4>> expected(Side * Side * fragments);
|
||||
for (std::uint32_t index = 0; index < expected.size(); ++index) {
|
||||
const auto sample = index % fragments;
|
||||
const auto pixel = index / fragments;
|
||||
const bool ones = uniform || ((pixel % Side) * 3u + (pixel / Side) * 5u + sample * 7u) % 2u == 0u;
|
||||
const std::array<std::uint16_t, 4> bits = ones ? std::array<std::uint16_t, 4>{0x3c00u, 0x3c00u, 0x3c00u, 0x3c00u} : std::array<std::uint16_t, 4>{0x3a00u, 0x3400u, 0x3800u, 0x3c00u};
|
||||
std::memcpy(linear.data() + index * sizeof(bits), bits.data(), sizeof(bits));
|
||||
expected[index] = ones ? Ones : Written;
|
||||
}
|
||||
layout.Tile(linear, std::as_writable_bytes(std::span(block.data, static_cast<std::size_t>(layout.Bytes()))));
|
||||
return expected;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL NoInitialStorageImage(VkPhysicalDevice physical, VkFormat format, VkImageType type, VkImageTiling tiling, VkImageUsageFlags usage, VkImageCreateFlags flags, VkImageFormatProperties* properties) {
|
||||
if ((flags & VK_IMAGE_CREATE_EXTENDED_USAGE_BIT) != 0 && (usage & VK_IMAGE_USAGE_STORAGE_BIT) != 0) return VK_ERROR_FORMAT_NOT_SUPPORTED;
|
||||
return imageFormatProperties(physical, format, type, tiling, usage, flags, properties);
|
||||
}
|
||||
|
||||
int InitialLifetime(AgcDriver::VulkanDevice& device) {
|
||||
using namespace AgcDriver::Graphics;
|
||||
const auto context = AgcDriver::VulkanDeviceTestAccess::ContextOf(device);
|
||||
if (!InitialCountSupported(context, VK_FORMAT_R16G16B16A16_SFLOAT, Samples)) return VulkanTestSkipped;
|
||||
device.WaitIdle();
|
||||
auto* previous = Recorder::Active();
|
||||
Require(previous != nullptr && previous->Device() == context.device, "initial lifetime fixture has no device recorder to restore");
|
||||
for (const bool uniform : {false, true}) {
|
||||
Block block(BlockBytes);
|
||||
if (uniform) StoreWords(block, block.bytes, FillOnes);
|
||||
const auto expected = uniform ? std::vector<std::array<float, 4>>(Side * Side * Samples, Ones) : StoreInitial(block, Samples, 0u, false);
|
||||
const auto color = DecodeTarget(block);
|
||||
const auto state = TargetState(color);
|
||||
{
|
||||
auto recorded = context;
|
||||
recorded.bufferPool = std::make_shared<BufferPool>(context);
|
||||
Recorder recorder(recorded);
|
||||
std::shared_ptr<void> owner;
|
||||
VkImageView view = VK_NULL_HANDLE;
|
||||
std::size_t initialBytes = 0;
|
||||
constexpr std::size_t uploadBytes = Side * Side * Samples * 8u;
|
||||
constexpr auto properties = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
|
||||
const auto usage = BufferPool::Usage(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, properties);
|
||||
{
|
||||
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
|
||||
recorder.Activate();
|
||||
view = MultisampleTargetView(recorded, color, owner);
|
||||
initialBytes = recorder.OpenKeptBytes();
|
||||
const auto premature = recorded.bufferPool->Take(uploadBytes, usage);
|
||||
if (premature) recorded.bufferPool->Put(*premature);
|
||||
Require(!premature.has_value(), "initial upload was recycled before submission completed");
|
||||
recorder.Submit();
|
||||
}
|
||||
recorder.Sync();
|
||||
Require(recorder.InFlightKeptBytes() == 0, "completed initial contents still count as in-flight kept bytes");
|
||||
auto released = recorded.bufferPool->Take(uploadBytes, usage);
|
||||
Require(released.has_value() != uniform, "initial upload was not returned to the caller's pool after completion, or uniform clear allocated an upload");
|
||||
if (released) recorded.bufferPool->Put(*released);
|
||||
for (int round = 0; round < 3; ++round) {
|
||||
{
|
||||
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
|
||||
std::shared_ptr<void> reusedOwner;
|
||||
Require(MultisampleTargetView(recorded, color, reusedOwner) == view && reusedOwner == owner, "initial-content accounting did not reuse its resident target");
|
||||
const auto targetBytes = recorder.OpenKeptBytes();
|
||||
Require(targetBytes != 0 && initialBytes == targetBytes + (uniform ? 0u : uploadBytes), "reused target still counts initial color snapshot or upload bytes");
|
||||
KeepMultisampleTarget_nid_no_patch(owner);
|
||||
Require(recorder.OpenKeptBytes() == targetBytes, "one target is counted twice within a submission");
|
||||
recorder.Submit();
|
||||
}
|
||||
recorder.Sync();
|
||||
}
|
||||
}
|
||||
previous->Activate();
|
||||
Draw(device, state);
|
||||
CheckSamples(state, LoadSamples(device, block), {}, expected);
|
||||
std::printf("native color initial lifetime: %s contents survive completion and three resident reuses without retaining initialization bytes\n", uniform ? "uniform" : "varying");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void InitialContents(AgcDriver::VulkanDevice& device) {
|
||||
using namespace AgcDriver::Graphics;
|
||||
const auto context = AgcDriver::VulkanDeviceTestAccess::ContextOf(device);
|
||||
std::uint32_t exercised = 0;
|
||||
for (const auto fragments : {2u, 4u, 8u}) {
|
||||
if (!InitialCountSupported(context, VK_FORMAT_R16G16B16A16_SFLOAT, fragments)) continue;
|
||||
for (const auto pipeBankXor : {0u, 0x8100u}) {
|
||||
for (const bool uniform : {false, true}) {
|
||||
Block block(BlockBytes);
|
||||
auto color = DecodeTarget(block, pipeBankXor);
|
||||
color.samples = fragments;
|
||||
const auto expected = StoreInitial(block, fragments, pipeBankXor, uniform);
|
||||
const std::vector<std::uint8_t> before(block.data, block.data + block.bytes);
|
||||
const auto state = TargetState(color);
|
||||
Draw(device, state);
|
||||
CheckSamples(state, LoadSamples(device, block, fragments, pipeBankXor), {}, expected);
|
||||
Require(std::equal(before.begin(), before.end(), block.data), "initialization or the draw changed guest color bytes or padding");
|
||||
std::printf("native color initial samples: fragments=%u xor=0x%x %s background survives first partial draw\n", fragments, pipeBankXor, uniform ? "uniform" : "varying pixel/fragment");
|
||||
++exercised;
|
||||
}
|
||||
}
|
||||
{
|
||||
Block block(BlockBytes);
|
||||
StoreWords(block, block.bytes, FillOnes);
|
||||
auto color = DecodeTarget(block);
|
||||
color.samples = fragments;
|
||||
auto noStorage = context;
|
||||
noStorage.storageImageMultisample = false;
|
||||
noStorage.storageImageWriteWithoutFormat = false;
|
||||
{
|
||||
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
|
||||
MultisampleTargetView(noStorage, color);
|
||||
}
|
||||
const auto state = TargetState(color);
|
||||
Draw(device, state);
|
||||
CheckSamples(state, LoadSamples(device, block, fragments), Ones);
|
||||
std::printf("native color initial samples: fragments=%u exact uniform clear without storage writes passed\n", fragments);
|
||||
++exercised;
|
||||
}
|
||||
{
|
||||
Block block(BlockBytes);
|
||||
auto color = DecodeTarget(block, 0x8100u);
|
||||
color.samples = fragments;
|
||||
const auto expected = StoreInitial(block, fragments, 0x8100u, false);
|
||||
auto linear = color;
|
||||
linear.tileMode = ColorTileMode::Linear;
|
||||
RequireInitialRefused(context, linear, "initial multisample color footprint is not implemented");
|
||||
for (const auto profile : {0u, 1u, 2u}) {
|
||||
auto unsupported = context;
|
||||
if (profile == 0) unsupported.storageImageMultisample = false;
|
||||
if (profile == 1) unsupported.storageImageWriteWithoutFormat = false;
|
||||
if (profile == 2) unsupported.detiler = nullptr;
|
||||
RequireInitialRefused(unsupported, color, "require enabled formatless multisampled storage image writes");
|
||||
}
|
||||
auto noRange = context;
|
||||
noRange.limits.maxStorageBufferRange = 0u;
|
||||
RequireInitialRefused(noRange, color, "storage range cannot hold an aligned element");
|
||||
auto noImage = context;
|
||||
imageFormatProperties = context.imageFormatProperties;
|
||||
noImage.imageFormatProperties = NoInitialStorageImage;
|
||||
RequireInitialRefused(noImage, color, "cannot initialize the multisampled color target through its unsigned storage view");
|
||||
auto chunked = context;
|
||||
chunked.limits.maxStorageBufferRange = 256u;
|
||||
{
|
||||
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
|
||||
MultisampleTargetView(chunked, color);
|
||||
}
|
||||
const auto state = TargetState(color);
|
||||
Draw(device, state);
|
||||
CheckSamples(state, LoadSamples(device, block, fragments, 0x8100u), {}, expected);
|
||||
std::printf("native color initial samples: fragments=%u five unsupported capability profiles and linear layout refused, restored 256-byte chunks passed\n", fragments);
|
||||
++exercised;
|
||||
}
|
||||
}
|
||||
Require(exercised != 0, "no supported native color initial-content case was exercised");
|
||||
}
|
||||
|
||||
alignas(256) std::array<std::uint32_t, 12> VertexValues{};
|
||||
|
||||
void DrawVertexInput(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::State& state, std::uint64_t address, std::uint32_t stride) {
|
||||
@@ -1300,6 +1491,11 @@ int main(int argc, char** argv) {
|
||||
try {
|
||||
auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--initial-lifetime") return InitialLifetime(*device);
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--initial") {
|
||||
InitialContents(*device);
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--native-fmask") {
|
||||
{
|
||||
const auto context = AgcDriver::VulkanDeviceTestAccess::ContextOf(*device);
|
||||
@@ -1348,15 +1544,15 @@ int main(int argc, char** argv) {
|
||||
std::puts("DIM 2D image_load, image_sample_lz and image_sample_l reads of a 4x color target return each pixel's sample 0");
|
||||
Block swapped(4u * BlockBytes);
|
||||
Draw(*device, TargetState(DecodeSwappedTarget(swapped)), 0, 2u, Unorm8PixelCode);
|
||||
const auto swappedSamples = LoadSamples(*device, swapped, Format8888Unorm);
|
||||
const auto swappedSamples = LoadSamples(*device, swapped, Samples, 0u, Format8888Unorm);
|
||||
CheckSwappedSamples(swappedSamples);
|
||||
CheckFirstSampleReads(*device, TargetDescriptor(swapped, SampleShift, Format8888Unorm), swappedSamples, true, "from a 4x BGRA8 target read through an RGBA8 descriptor");
|
||||
CheckFirstSampleReads(*device, TargetDescriptor(swapped, SampleShift, 0u, Format8888Unorm), swappedSamples, true, "from a 4x BGRA8 target read through an RGBA8 descriptor");
|
||||
std::puts("a 4x BGRA8 color target loaded through an RGBA8 texture descriptor reads its drawn color with red and blue swapped");
|
||||
Block packedFloat(4u * BlockBytes);
|
||||
Draw(*device, TargetState(DecodePackedFloatTarget(packedFloat)));
|
||||
const auto packedSamples = LoadSamples(*device, packedFloat, Format11_11_10Float);
|
||||
const auto packedSamples = LoadSamples(*device, packedFloat, Samples, 0u, Format11_11_10Float);
|
||||
CheckPackedFloatSamples(packedSamples);
|
||||
CheckFirstSampleReads(*device, TargetDescriptor(packedFloat, SampleShift, Format11_11_10Float), packedSamples, true, "from a 4x 10_11_11 float target read through a format 36 descriptor");
|
||||
CheckFirstSampleReads(*device, TargetDescriptor(packedFloat, SampleShift, 0u, Format11_11_10Float), packedSamples, true, "from a 4x 10_11_11 float target read through a format 36 descriptor");
|
||||
std::puts("a 4x 10_11_11 float color target loaded through a format 36 (11_11_10 float) texture descriptor reads its drawn color");
|
||||
Block integer(4u * BlockBytes);
|
||||
Block signedFill(BlockBytes);
|
||||
@@ -1364,11 +1560,11 @@ int main(int argc, char** argv) {
|
||||
std::puts("DIM 2D reads of 4x 8_8_8_8_UINT and 8_8_8_8_SINT targets not tested: sampledImageIntegerSampleCounts lacks VK_SAMPLE_COUNT_4_BIT");
|
||||
} else {
|
||||
Draw(*device, TargetState(DecodeUintTarget(integer)), 0, 2u, Uint8PixelCode);
|
||||
const auto integerSamples = LoadSamples(*device, integer, Format8888Uint);
|
||||
const auto integerSamples = LoadSamples(*device, integer, Samples, 0u, Format8888Uint);
|
||||
CheckUintSamples(integerSamples);
|
||||
CheckFirstSampleReads(*device, TargetDescriptor(integer, SampleShift, Format8888Uint), integerSamples, true, "from a drawn 4x 8_8_8_8_UINT target");
|
||||
CheckFirstSampleReads(*device, TargetDescriptor(integer, SampleShift, 0u, Format8888Uint), integerSamples, true, "from a drawn 4x 8_8_8_8_UINT target");
|
||||
StoreWords(signedFill, BlockBytes, FilledSint8);
|
||||
CheckFirstSampleReads(*device, TargetDescriptor(signedFill, SampleShift, Format8888Sint), std::vector<std::array<float, 4>>(Side * Side * Samples, std::bit_cast<std::array<float, 4>>(FilledSint8Texel)), false, "from 8_8_8_8_SINT color memory no draw rendered to");
|
||||
CheckFirstSampleReads(*device, TargetDescriptor(signedFill, SampleShift, 0u, Format8888Sint), std::vector<std::array<float, 4>>(Side * Side * Samples, std::bit_cast<std::array<float, 4>>(FilledSint8Texel)), false, "from 8_8_8_8_SINT color memory no draw rendered to");
|
||||
std::puts("DIM 2D reads of a drawn 4x 8_8_8_8_UINT target and of 8_8_8_8_SINT color memory no draw rendered to return each pixel's sample 0 bit for bit");
|
||||
}
|
||||
CheckOverwrittenTarget(*device);
|
||||
@@ -1462,13 +1658,9 @@ int main(int argc, char** argv) {
|
||||
for (const auto& [mode, tileMode] : {std::pair{TileRenderTarget, AgcDriver::Graphics::TextureTileMode::kR64KBX}, std::pair{TileZ64KBX, AgcDriver::Graphics::TextureTileMode::kZ64KBX}}) {
|
||||
Block varying(BlockBytes);
|
||||
const auto expected = StoreVaryingUnorm(varying, tileMode);
|
||||
CheckLoaded(LoadSamples(*device, varying, Format8888Unorm, mode), expected, "from varying color memory of a " + std::to_string(mode) + " swizzle surface no draw rendered to");
|
||||
CheckLoaded(LoadSamples(*device, varying, Samples, 0u, Format8888Unorm, mode), expected, "from varying color memory of a " + std::to_string(mode) + " swizzle surface no draw rendered to");
|
||||
}
|
||||
Block varyingD(BlockBytes);
|
||||
StoreWords(varyingD, colorBytes, FillOnes);
|
||||
std::memcpy(varyingD.data, &odd, sizeof(odd));
|
||||
RequireRefused(*device, TargetDescriptor(varyingD, SampleShift, Format16161616Float, TileD64KBX), "non-uniform multisampled guest color tiling is not implemented", "loading varying D_X color memory no draw rendered to");
|
||||
std::puts("loads of R_X and Z_X color memory no draw rendered to return each sample's guest color, and varying D_X color memory is refused");
|
||||
std::puts("loads of R_X and Z_X color memory no draw rendered to return each sample's guest color");
|
||||
const auto oneSample = [](std::uint32_t, std::uint32_t, std::uint32_t sample) { return sample == StoredSample; };
|
||||
const auto drawnBefore = LoadSamples(*device, block);
|
||||
StoreSamples(*device, TargetDescriptor(block, SampleShift), SampleWrites(oneSample));
|
||||
@@ -1503,14 +1695,15 @@ int main(int argc, char** argv) {
|
||||
for (const auto mode : {TileZ64KBX, TileD64KBX}) {
|
||||
Block swizzled(BlockBytes);
|
||||
StoreWords(swizzled, colorBytes, FillOnes);
|
||||
CheckUniform(LoadSamples(*device, swizzled, Format16161616Float, mode), Ones, "from the uniform memory of a " + std::to_string(mode) + " swizzle surface no draw rendered to");
|
||||
CheckUniform(LoadSamples(*device, swizzled, Samples, 0u, Format16161616Float, mode), Ones, "from the uniform memory of a " + std::to_string(mode) + " swizzle surface no draw rendered to");
|
||||
}
|
||||
const auto htileBytes = AgcDriver::Graphics::HtileMetadataBytes({Side, Side});
|
||||
Require(htileBytes <= BlockBytes, "the HTILE of the 4x texture does not fit one block");
|
||||
Block cleared(2u * BlockBytes);
|
||||
StoreWords(cleared, colorBytes, FillOnes);
|
||||
cleared.data[0] ^= 1u;
|
||||
const auto clearedHtile = cleared.Address() + BlockBytes;
|
||||
CheckUniform(LoadSamples(*device, CompressedDescriptor(cleared, TileZ64KBX, clearedHtile)), {}, "from a Z swizzle surface whose HTILE is all cleared to 0.0 over memory of other values");
|
||||
CheckUniform(LoadSamples(*device, CompressedDescriptor(cleared, TileZ64KBX, clearedHtile)), {}, "from a Z swizzle surface whose HTILE is all cleared to 0.0 over nonuniform color memory");
|
||||
AgcDriver::Graphics::NoteColorMetadataFill(clearedHtile, sizeof(std::uint32_t), 0u);
|
||||
RequireRefused(*device, CompressedDescriptor(cleared, TileZ64KBX, clearedHtile), "over the HTILE", "loading a Z swizzle texture after a fill over part of its HTILE");
|
||||
AgcDriver::Graphics::NoteColorMetadataFill(clearedHtile, static_cast<std::size_t>(htileBytes), 0u);
|
||||
@@ -1529,7 +1722,7 @@ int main(int argc, char** argv) {
|
||||
RequireRefused(*device, CompressedDescriptor(display, TileD64KBX, display.Address() + BlockBytes), "compression metadata", "loading a D swizzle multisampled texture no draw rendered to with compression metadata");
|
||||
Block standard(BlockBytes);
|
||||
StoreWords(standard, colorBytes, FillOnes);
|
||||
RequireRefused(*device, TargetDescriptor(standard, SampleShift, Format16161616Float, TileS64KBX), "has no modeled multisampled color footprint", "loading an S swizzle multisampled texture no draw rendered to");
|
||||
RequireRefused(*device, TargetDescriptor(standard, SampleShift, 0u, Format16161616Float, TileS64KBX), "has no modeled multisampled color footprint", "loading an S swizzle multisampled texture no draw rendered to");
|
||||
device->WaitIdle();
|
||||
AgcDriver::Graphics::ClearMultisampleTargets(device->Device());
|
||||
CheckBdaSpan(*device, block, 0, false);
|
||||
|
||||
@@ -348,6 +348,9 @@
|
||||
- [Flip submissions hold the next flip until their work completed](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp) (libSceAgcDriver) - a queue-0 submission that carries a flip in its command buffer returns only after every earlier flip submission ran to its end and its recorded GPU work completed and was reaped, so the labels a frame writes after its flip are in memory when the title reads them after its next submission. A CPU flip (`sceVideoOutSubmitFlip`) does not wait, and the next flip is still released before the previous one is presented. Flips raise no graphics end-of-pipe event (from the PS5 13.60 kernel: a flip's interrupt context is always typed and goes to the display path, never to this event). Not measured: how far the console lets a title run ahead of frames its GPU has not finished.
|
||||
- [Periodic pipeline cache saves skip busy intervals](../../../core/libs/prx/libSceAgcDriver/Graphics/src/PipelineCache.cpp) (libSceAgcDriver) - the saver thread snapshots the Vulkan pipeline cache only when no pipeline creation holds it (`TryLockForSnapshot`); otherwise it skips that 10 s interval. A blocking snapshot queued behind a long creation (an NVIDIA compile of a 1.5M-word compute pipeline runs for minutes) makes the writer-preferring `shared_mutex` hold every later pipeline creation on every queue until that compile ends, which froze Yōtei's (PPSA26344) loading at frame 797-799 for the whole compile. While creations overlap without a gap, no periodic save happens, so a crash in that stretch loses the pipelines built since the last save; `SaveNow` and the teardown save still wait for the lock. How often busy stretches skip saves in practice is not measured.
|
||||
- [Registration preparation at submit](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp) (libSceAgcDriver) - `Driver::Submit` waits for every registration-time shader preparation queued before the call, not only those of the shaders the commands use, so a submission behind a long queue of unrelated preparations waits for all of them on the submitting thread. Waiting there instead of in the queue worker keeps the GPU side of a submission short: a title that sees a submission take seconds treats the GPU as hung (Goat Simulator 3, PPSA04159, then stops submitting and its garbage collector fails a 10 s timeout). On the console shaders are compiled ahead of time, so neither wait exists; how long a title tolerates a slow submit call is not known.
|
||||
|
||||
- [Native multisample color initialization](../../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp) does not import general compression metadata or publish guest color; console equivalence and large-title memory costs remain unverified. Preserving the latest samples of an overlapping, differently described native owner requires the guest-publication path in #6995. Uncompressed single-layer SW_64KB_R_X contents without CMASK use a fragment-preserving unsigned storage upload at 2/4/8 fragments, or an exact clear when the complete footprint repeats one representable texel. Different padding also takes the upload path. The native image retains its declared extent; its owner retains the complete snapshot and staging bytes. Unsupported layouts, formats, subresources and device capabilities throw. Existing proven DCC/CMASK clear paths are retained.
|
||||
|
||||
- [Resource cache entries holding released textures](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver) - a sampled texture the texture cache drops while something else holds it, or makes without caching (`admitTexture` refusing a first use), is marked released, and `ResourceCache::Insert` refuses descriptor sets that hold one and removes the entries that do. The removal waits for the next insert: until then `Find` still returns those entries and their textures stay alive. One process-wide flag starts the sweep, so while the sampled cache is over its budget every insert scans all entries (1,024 at start, up to 16,384 after growth, or `APS5_RESOURCE_CACHE_ENTRIES`, each with its textures); that cost is not measured. When `ReleaseTextureCaches` runs on device memory exhaustion, the sampled textures these entries hold are only marked released and are freed at the next insert, so the immediate allocation retry gets no memory back from them. A draw or dispatch whose textures the cache refused builds its descriptor sets again on each use for as long as the cache stays full; the count bounds (`APS5_RESOURCE_CACHE_ENTRIES`, `APS5_RESOURCE_CACHE_TABLE_ELEMENTS`) still bound nothing by bytes, and sampled textures made outside the texture cache (the disabled cache, depth-surface copies) are never marked. Measured on one title on one RTX 4090: with the default bounds, sampled textures held outside the cache peaked at 2,836 MiB (7,830 MiB before) and device-local heap 0 stayed under its 22.9 GB budget for the whole run (26,357 MiB before, which aborted with `VK_ERROR_OUT_OF_DEVICE_MEMORY` at the wolf scene). Whether the extra descriptor-set builds cost frame time elsewhere is not measured.
|
||||
- [Host import refresh](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp) (libSceAgcDriver): after a change to the guest allocation registry, an import is checked again only when its import window, or its own range when larger, touches a range that changed since the last check, or when it was made since then; every import is checked when the window is 0 or the window or the budget changed. An unchanged import whose run could now be larger, because a remembered refusal no longer holds, is kept until its window changes or a request for the larger run retires it; before, any registry change anywhere retired it. Remembered failures and refusals are dropped the same way and stay checked where they are used.
|
||||
|
||||
|
||||
Reference in new issue
Block a user