blob: 1e2c0e4e11ea37b31039bfe530db5a80dc3fcb17 [file] [edit]
/* Copyright (c) 2026 The Khronos Group Inc.
* Copyright (c) 2026 Valve Corporation
* Copyright (c) 2026 LunarG, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "sync/sync_dynamic_rendering.h"
#include "sync/sync_image.h"
#include "sync/sync_validation.h"
#include "state_tracker/pipeline_state.h"
#include "utils/vk_api_utils.h"
#include <vulkan/utility/vk_format_utils.h>
namespace syncval {
constexpr SyncAccessIndex kResolveRead = SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_READ;
constexpr SyncAccessIndex kResolveWrite = SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE;
constexpr SyncOrdering kColorResolveOrder = SyncOrdering::kColorAttachment;
// Although depth resolve runs on the color attachment output stage and uses color accesses, depth accesses
// still participate in the ordering. That's why using raster and not only color attachment ordering
constexpr SyncOrdering kDepthStencilResolveOrder = SyncOrdering::kRaster;
constexpr SyncOrdering kStoreOrder = SyncOrdering::kRaster;
RenderingAttachment::RenderingAttachment(const SyncValidator& validator, const VkRenderingAttachmentInfo& info,
const AttachmentType type)
: type(type), load_op(info.loadOp), store_op(info.storeOp), resolve_mode(info.resolveMode) {
view = validator.Get<vvl::ImageView>(info.imageView);
if (view && info.resolveImageView != VK_NULL_HANDLE && resolve_mode != VK_RESOLVE_MODE_NONE) {
resolve_view = validator.Get<vvl::ImageView>(info.resolveImageView);
}
}
ImageRangeGen RenderingAttachment::GetRangeGen(const VkRect2D& render_area, uint32_t view_mask) const {
// VkRenderingAttachmentInfo::imageView is allowed to be VK_NULL_HANDLE
if (!view) {
return {};
}
// Multiview is disabled: return range gen for render area
if (view_mask == 0) {
const VkOffset3D offset = CastTo3D(render_area.offset);
const VkExtent3D extent = CastTo3D(render_area.extent);
if (type == AttachmentType::kColor) {
return MakeImageRangeGen(*view, offset, extent);
} else if (type == AttachmentType::kDepth) {
return MakeImageRangeGen(*view, offset, extent, VK_IMAGE_ASPECT_DEPTH_BIT);
} else {
return MakeImageRangeGen(*view, offset, extent, VK_IMAGE_ASPECT_STENCIL_BIT);
}
}
// Initialize range gen based on view mask. The render area is not applied on purpose:
// load/store/resolve operations can access the entire attachment subresource.
// The non-multiview path keeps the original render-area model.
if (type == AttachmentType::kColor) {
return MakeImageRangeGen(*view, view_mask);
} else if (type == AttachmentType::kDepth) {
return MakeImageRangeGen(*view, view_mask, VK_IMAGE_ASPECT_DEPTH_BIT);
} else {
return MakeImageRangeGen(*view, view_mask, VK_IMAGE_ASPECT_STENCIL_BIT);
}
}
ImageRangeGen RenderingAttachment::GetResolveRangeGen(const VkRect2D& render_area) const {
const VkOffset3D offset = CastTo3D(render_area.offset);
const VkExtent3D extent = CastTo3D(render_area.extent);
if (type == AttachmentType::kColor) {
return MakeImageRangeGen(*resolve_view, offset, extent);
} else if (type == AttachmentType::kDepth) {
// Select only the depth aspect
return MakeImageRangeGen(*resolve_view, offset, extent, VK_IMAGE_ASPECT_DEPTH_BIT);
} else {
return MakeImageRangeGen(*resolve_view, offset, extent, VK_IMAGE_ASPECT_STENCIL_BIT);
}
}
SyncAccessIndex RenderingAttachment::GetLoadUsage() const {
if (load_op == VK_ATTACHMENT_LOAD_OP_NONE) {
return SYNC_ACCESS_INDEX_NONE;
} else if (type == AttachmentType::kColor) {
return load_op == VK_ATTACHMENT_LOAD_OP_LOAD ? SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_READ
: SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE;
} else { // depth and stencil ops are the same
return load_op == VK_ATTACHMENT_LOAD_OP_LOAD ? SYNC_EARLY_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_READ
: SYNC_EARLY_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE;
}
}
SyncAccessIndex RenderingAttachment::GetStoreUsage() const {
if (store_op == VK_ATTACHMENT_STORE_OP_NONE) {
return SYNC_ACCESS_INDEX_NONE;
} else if (type == AttachmentType::kColor) {
return SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE;
} else { // depth and stencil ops are the same
return SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE;
}
}
SyncOrdering RenderingAttachment::GetOrdering() const {
return type == AttachmentType::kColor ? SyncOrdering::kColorAttachment : SyncOrdering::kDepthStencilAttachment;
}
Location RenderingAttachment::GetLocation(uint32_t attachment_index, vvl::Func function) const {
if (type == AttachmentType::kColor) {
return Location(function, vvl::Struct::VkRenderingAttachmentInfo, vvl::Field::pColorAttachments, attachment_index);
} else if (type == AttachmentType::kDepth) {
return Location(function, vvl::Struct::VkRenderingAttachmentInfo, vvl::Field::pDepthAttachment);
} else {
assert(type == AttachmentType::kStencil);
return Location(function, vvl::Struct::VkRenderingAttachmentInfo, vvl::Field::pStencilAttachment);
}
}
bool RenderingAttachment::IsWriteable(bool depth_write, bool stencil_write) const {
if (!view) {
return false;
}
if (type == AttachmentType::kColor) {
return true;
} else if (type == AttachmentType::kDepth) {
return depth_write && vkuFormatHasDepth(view->create_info.format);
} else {
assert(type == AttachmentType::kStencil);
return stencil_write && vkuFormatHasStencil(view->create_info.format);
}
}
std::vector<RenderingAttachment> CollectAttachments(const SyncValidator& validator, const VkRenderingInfo& rendering_info) {
uint32_t attachment_count = rendering_info.colorAttachmentCount;
attachment_count += rendering_info.pDepthAttachment ? 1 : 0;
attachment_count += rendering_info.pStencilAttachment ? 1 : 0;
std::vector<RenderingAttachment> attachments;
attachments.reserve(attachment_count);
for (uint32_t i = 0; i < rendering_info.colorAttachmentCount; i++) {
attachments.emplace_back(validator, rendering_info.pColorAttachments[i], AttachmentType::kColor);
}
if (rendering_info.pDepthAttachment) {
attachments.emplace_back(validator, *rendering_info.pDepthAttachment, AttachmentType::kDepth);
}
if (rendering_info.pStencilAttachment) {
attachments.emplace_back(validator, *rendering_info.pStencilAttachment, AttachmentType::kStencil);
}
return attachments;
}
void RenderingInstance::InitViewGens(std::vector<ImageRangeGen>& view_gen_storage) {
view_gen_storage.clear();
view_gen_storage.reserve(attachments.size());
for (const RenderingAttachment& attachment : attachments) {
view_gen_storage.emplace_back(attachment.GetRangeGen(render_area, view_mask));
}
view_gens = view_gen_storage; // init span
}
const vvl::ImageView* RenderingInstance::GetClearAttachmentView(const VkClearAttachment& clear_attachment) const {
if (clear_attachment.aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) {
if (clear_attachment.colorAttachment < color_attachment_count) {
return attachments[clear_attachment.colorAttachment].view.get();
}
} else if (clear_attachment.aspectMask & kDepthStencilAspects) {
if (attachments.size() > color_attachment_count) {
// If both depth and stencil attachments are defined they must both point to the same view
return attachments.back().view.get();
}
}
return nullptr;
}
bool RenderingInstance::ValidateBeginRendering(const SyncEnvironment& env, const AccessContext& access_context,
const CommandBufferContext& cb_context, ResourceUsageTag replay_tag,
const Location& loc, uint32_t render_pass_instance_id) const {
assert(view_gens.size() == attachments.size());
bool skip = false;
const SyncValidator& validator = env.validator;
if (flags & VK_RENDERING_RESUMING_BIT) {
// Load operations do not happen when resuming
return skip;
}
for (size_t i = 0; i < attachments.size(); i++) {
const RenderingAttachment& attachment = attachments[i];
const SyncAccessIndex load_index = attachment.GetLoadUsage();
if (load_index == SYNC_ACCESS_INDEX_NONE) {
continue;
}
const AttachmentAccess attachment_access = {AttachmentAccessType::LoadOp, attachment.GetOrdering(),
render_pass_instance_id};
ImageRangeGen range_gen = view_gens[i];
const HazardResult hazard = access_context.DetectAttachmentHazard(range_gen, load_index, attachment_access, env.queue_id);
if (hazard.IsHazard()) {
const LogObjectList objlist = BaseObjectList(env, cb_context, attachment.view->Handle());
std::ostringstream ss;
ss << vvl::String(vvl::Field::pRenderingInfo) << ".";
ss << attachment.GetLocation(uint32_t(i)).Fields();
ss << " (" << validator.FormatHandle(attachment.view->Handle());
ss << ", loadOp " << string_VkAttachmentLoadOp(attachment.load_op) << ")";
std::string resource_description = ss.str();
const std::string error = validator.error_messages_.BeginRenderingError(env, hazard, cb_context, replay_tag, loc,
resource_description, attachment.load_op);
skip |= validator.SyncError(hazard.Hazard(), objlist, loc, error);
if (skip) {
break;
}
}
}
return skip;
}
void RenderingInstance::RecordBeginRendering(AccessContext& access_context, uint32_t render_pass_instance_id, ResourceUsageTag tag,
QueueId queue_id) const {
assert(view_gens.size() == attachments.size());
if ((flags & VK_RENDERING_RESUMING_BIT) != 0) {
return;
}
for (size_t i = 0; i < attachments.size(); i++) {
const RenderingAttachment& attachment = attachments[i];
const SyncAccessIndex load_index = attachment.GetLoadUsage();
if (load_index == SYNC_ACCESS_INDEX_NONE) {
continue;
}
ImageRangeGen range_gen = view_gens[i];
const AttachmentAccess attachment_access = {AttachmentAccessType::LoadOp, attachment.GetOrdering(),
render_pass_instance_id};
access_context.UpdateAttachmentAccessState(range_gen, load_index, attachment_access, ResourceUsageTagEx{tag}, queue_id);
}
}
bool RenderingInstance::ValidateEndRendering(const SyncEnvironment& env, const AccessContext& access_context,
const CommandBufferContext& cb_context, ResourceUsageTag replay_tag,
const Location& loc, uint32_t render_pass_instance_id) const {
bool skip = false;
const SyncValidator& validator = env.validator;
if ((flags & VK_RENDERING_SUSPENDING_BIT) != 0) {
// Resolve/store operations do not happen when suspending
return skip;
}
for (uint32_t i = 0; i < attachments.size(); i++) {
const RenderingAttachment& attachment = attachments[i];
auto attachment_description = [&validator, &attachment, i](const auto& view, std::ostringstream& ss) {
ss << vvl::String(vvl::Field::pRenderingInfo) << ".";
ss << attachment.GetLocation(uint32_t(i)).Fields();
ss << " (" << validator.FormatHandle(view->Handle());
};
// Validate resolve. Resolve view is available only when resolve operation is enabled
if (attachment.resolve_view) {
const bool is_color = attachment.type == AttachmentType::kColor;
const SyncOrdering kResolveOrder = is_color ? kColorResolveOrder : kDepthStencilResolveOrder;
const AttachmentAccess resolve_read_access = {AttachmentAccessType::ResolveRead, kResolveOrder,
render_pass_instance_id};
ImageRangeGen view_gen = view_gens[i];
HazardResult hazard = access_context.DetectAttachmentHazard(view_gen, kResolveRead, resolve_read_access, env.queue_id);
if (hazard.IsHazard()) {
const LogObjectList objlist = BaseObjectList(env, cb_context, attachment.view->Handle());
std::ostringstream ss;
attachment_description(attachment.view, ss);
ss << ", resolveMode " << string_VkResolveModeFlagBits(attachment.resolve_mode) << ")";
const std::string resource_description = ss.str();
const std::string error = validator.error_messages_.EndRenderingResolveError(
env, hazard, cb_context, replay_tag, loc, resource_description, attachment.resolve_mode, false);
skip |= validator.SyncError(hazard.Hazard(), objlist, loc, error);
if (skip) {
break;
}
}
const AttachmentAccess resolve_write_access = {AttachmentAccessType::ResolveWrite, kResolveOrder,
render_pass_instance_id};
ImageRangeGen resolve_gen = attachment.GetResolveRangeGen(render_area);
hazard = access_context.DetectAttachmentHazard(resolve_gen, kResolveWrite, resolve_write_access, env.queue_id);
if (hazard.IsHazard()) {
const LogObjectList objlist = BaseObjectList(env, cb_context, attachment.resolve_view->Handle());
std::ostringstream ss;
attachment_description(attachment.resolve_view, ss);
ss << ", resolveMode " << string_VkResolveModeFlagBits(attachment.resolve_mode) << ")";
const std::string resource_description = ss.str();
const std::string error = validator.error_messages_.EndRenderingResolveError(
env, hazard, cb_context, replay_tag, loc, resource_description, attachment.resolve_mode, true);
skip |= validator.SyncError(hazard.Hazard(), objlist, loc, error);
if (skip) {
break;
}
}
}
// Validate store
const SyncAccessIndex store_access = attachment.GetStoreUsage();
if (store_access != SYNC_ACCESS_INDEX_NONE) {
const AttachmentAccess attachment_access = {AttachmentAccessType::StoreOp, kStoreOrder, render_pass_instance_id};
ImageRangeGen view_gen = view_gens[i];
HazardResult hazard = access_context.DetectAttachmentHazard(view_gen, store_access, attachment_access, env.queue_id);
if (hazard.IsHazard()) {
const LogObjectList objlist = BaseObjectList(env, cb_context, attachment.view->Handle());
std::ostringstream ss;
attachment_description(attachment.view, ss);
ss << ", storeOp " << string_VkAttachmentStoreOp(attachment.store_op) << ")";
const std::string resource_description = ss.str();
const std::string error = validator.error_messages_.EndRenderingStoreError(
env, hazard, cb_context, replay_tag, loc, resource_description, attachment.store_op);
skip |= validator.SyncError(hazard.Hazard(), objlist, loc, error);
if (skip) {
break;
}
}
}
}
return skip;
}
void RenderingInstance::RecordEndRendering(AccessContext& access_context, uint32_t render_pass_instance_id, ResourceUsageTag tag,
QueueId queue_id) const {
if ((flags & VK_RENDERING_SUSPENDING_BIT) != 0) {
return;
}
for (size_t i = 0; i < attachments.size(); i++) {
const RenderingAttachment& attachment = attachments[i];
if (attachment.resolve_view) {
const bool is_color = attachment.type == AttachmentType::kColor;
const SyncOrdering kResolveOrder = is_color ? kColorResolveOrder : kDepthStencilResolveOrder;
const AttachmentAccess resolve_read_access = {AttachmentAccessType::ResolveRead, kResolveOrder,
render_pass_instance_id};
ImageRangeGen view_gen = view_gens[i];
access_context.UpdateAttachmentAccessState(view_gen, kResolveRead, resolve_read_access, ResourceUsageTagEx{tag},
queue_id);
const AttachmentAccess resolve_write_access = {AttachmentAccessType::ResolveWrite, kResolveOrder,
render_pass_instance_id};
ImageRangeGen resolve_gen = attachment.GetResolveRangeGen(render_area);
access_context.UpdateAttachmentAccessState(resolve_gen, kResolveWrite, resolve_write_access, ResourceUsageTagEx{tag},
queue_id);
}
const SyncAccessIndex store_index = attachment.GetStoreUsage();
if (store_index != SYNC_ACCESS_INDEX_NONE) {
const AttachmentAccess attachment_access = {AttachmentAccessType::StoreOp, kStoreOrder, render_pass_instance_id};
ImageRangeGen view_gen = view_gens[i];
access_context.UpdateAttachmentAccessState(view_gen, store_index, attachment_access, ResourceUsageTagEx{tag}, queue_id);
}
}
}
bool RenderingInstance::ValidateDrawAttachments(const SyncEnvironment& env, const AccessContext& access_context,
const CommandBufferContext& cb_context, ResourceUsageTag replay_tag,
const Location& loc, uint32_t render_pass_instance_id,
const vvl::Pipeline* pipeline, bool depth_write, bool stencil_write) const {
bool skip = false;
if (!pipeline || pipeline->RasterizationDisabled()) {
return skip;
}
const auto& list = pipeline->fs_writable_output_location_list;
const SyncValidator& validator = env.validator;
for (const uint32_t output_location : list) {
if (output_location >= color_attachment_count) {
continue;
}
const auto& attachment = attachments[output_location];
if (!attachment.IsWriteable(depth_write, stencil_write)) {
continue;
}
const AttachmentAccess attachment_access{AttachmentAccessType::Access, SyncOrdering::kColorAttachment,
render_pass_instance_id, vvl::kNoIndex32};
ImageRangeGen view_gen = view_gens[output_location];
HazardResult hazard = access_context.DetectAttachmentHazard(view_gen, SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE,
attachment_access, env.queue_id);
if (hazard.IsHazard()) {
const LogObjectList objlist = BaseObjectList(env, cb_context, attachment.view->Handle());
const Location attachment_loc = attachment.GetLocation(output_location, loc.function);
const Location location = replay_tag == kInvalidTag ? attachment_loc.dot(vvl::Field::imageView) : loc;
const std::string error = validator.error_messages_.DynamicRenderingAttachmentError(
env, hazard, cb_context, replay_tag, loc, validator.FormatHandle(*attachment.view));
skip |= validator.SyncError(hazard.Hazard(), objlist, location, error);
}
}
// NOTE: these are the old todos, need to reevaluate which ones are needed
// TODO:fixup this and Subpass attachment to correct map the various depth stencil enables/reads vs. writes
// TODO: Add layout based read/vs. write selection.
// TODO: Read operations for both depth and stencil are possible in the future.
// TODO: Add EARLY stage detection based on ExecutionMode.
for (size_t i = color_attachment_count; i < attachments.size(); i++) {
const RenderingAttachment& attachment = attachments[i];
const bool writeable = attachment.IsWriteable(depth_write, stencil_write);
if (writeable) {
const AttachmentAccess attachment_access{AttachmentAccessType::Access, SyncOrdering::kDepthStencilAttachment,
render_pass_instance_id, vvl::kNoIndex32};
ImageRangeGen view_gen = view_gens[i];
HazardResult hazard = access_context.DetectAttachmentHazard(
view_gen, SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE, attachment_access, env.queue_id);
if (hazard.IsHazard()) {
const LogObjectList objlist = BaseObjectList(env, cb_context, attachment.view->Handle());
const Location attachment_loc = attachment.GetLocation(uint32_t(i), loc.function);
const Location location = replay_tag == kInvalidTag ? attachment_loc.dot(vvl::Field::imageView) : loc;
const std::string error = validator.error_messages_.DynamicRenderingAttachmentError(
env, hazard, cb_context, replay_tag, loc, validator.FormatHandle(*attachment.view));
skip |= validator.SyncError(hazard.Hazard(), objlist, location, error);
}
}
}
return skip;
}
void RenderingInstance::RecordDrawAttachments(AccessContext& access_context, uint32_t render_pass_instance_id,
const vvl::Pipeline* pipeline, bool depth_write, bool stencil_write,
ResourceUsageTag tag, QueueId queue_id) const {
if (!pipeline || pipeline->RasterizationDisabled()) {
return;
}
const auto& list = pipeline->fs_writable_output_location_list;
for (const uint32_t output_location : list) {
if (output_location >= color_attachment_count) {
continue;
}
const auto& attachment = attachments[output_location];
if (!attachment.IsWriteable(depth_write, stencil_write)) {
continue;
}
const AttachmentAccess attachment_access{AttachmentAccessType::Access, SyncOrdering::kColorAttachment,
render_pass_instance_id, vvl::kNoIndex32};
ImageRangeGen view_gen = view_gens[output_location];
access_context.UpdateAttachmentAccessState(view_gen, SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE, attachment_access,
ResourceUsageTagEx{tag}, queue_id);
}
// NOTE: these are the old todos, need to reevaluate which ones are needed
// TODO: fixup this and Subpass attachment to correct map the various depth stencil enables/reads vs. writes
// TODO: Add layout based read/vs. write selection.
// TODO: Read operations for both depth and stencil are possible in the future.
// TODO: Add EARLY stage detection based on ExecutionMode.
const uint32_t attachment_count = static_cast<uint32_t>(attachments.size());
for (uint32_t i = color_attachment_count; i < attachment_count; i++) {
const RenderingAttachment& attachment = attachments[i];
const bool writeable = attachment.IsWriteable(depth_write, stencil_write);
if (writeable) {
const AttachmentAccess attachment_access{AttachmentAccessType::Access, SyncOrdering::kDepthStencilAttachment,
render_pass_instance_id, vvl::kNoIndex32};
ImageRangeGen view_gen = view_gens[i];
access_context.UpdateAttachmentAccessState(view_gen, SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE,
attachment_access, ResourceUsageTagEx{tag}, queue_id);
}
}
}
} // namespace syncval