[vk, opengl] Prevent GPU draw call if CBUF binding fails (cbuf0 error handling) (#2)
All checks were successful
eden-build / source (push) Successful in 6m50s
eden-build / linux (push) Successful in 28m30s
eden-build / windows (msvc) (push) Successful in 32m22s
eden-build / android (push) Successful in 34m47s

Add defensive checks to cancel draw calls early if any graphics storage buffer (CBUF) fails to bind properly.

- Modified BindGraphicsStorageBuffer to return false on invalid buffer ID.,
- ConfigureImpl (both OpenGL and Vulkan) now propagates binding failure.,
- Pipeline::Configure returns false if CBUF binding fails.,
- PrepareDraw cancels rendering if pipeline configuration fails.,

This avoids undefined GPU behavior, draw corruption, or crashes caused by uninitialized or invalid constant buffer (CBUF0) access, particularly in games with faulty or missing shader bindings.
Eden Collaborator: <edencollaborator@eden-emu.org>
Authored-by: CamilleLaVey <camillelavey@eden-emu.org>
Signed-off-by: Bix <bix@bixed.xyz>

Co-authored-by: Bix <114880614+Bixbr@users.noreply.github.com>
Co-authored-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2
Co-authored-by: Ghost <>
Co-committed-by: Ghost <>
This commit is contained in:
Ghost 2025-07-13 19:27:39 +02:00 committed by crueter
parent 1cd51d6545
commit c47f6615d3
No known key found for this signature in database
GPG key ID: 425ACD2D4830EBC6
8 changed files with 44 additions and 21 deletions

View file

@ -416,7 +416,7 @@ void BufferCache<P>::UnbindGraphicsStorageBuffers(size_t stage) {
}
template <class P>
void BufferCache<P>::BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index, u32 cbuf_index,
bool BufferCache<P>::BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index, u32 cbuf_index,
u32 cbuf_offset, bool is_written) {
channel_state->enabled_storage_buffers[stage] |= 1U << ssbo_index;
channel_state->written_storage_buffers[stage] |= (is_written ? 1U : 0U) << ssbo_index;
@ -425,6 +425,7 @@ void BufferCache<P>::BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index,
const GPUVAddr ssbo_addr = cbufs.const_buffers[cbuf_index].address + cbuf_offset;
channel_state->storage_buffers[stage][ssbo_index] =
StorageBufferBinding(ssbo_addr, cbuf_index, is_written);
return (channel_state->storage_buffers[stage][ssbo_index].buffer_id != NULL_BUFFER_ID);
}
template <class P>

View file

@ -235,7 +235,7 @@ public:
void UnbindGraphicsStorageBuffers(size_t stage);
void BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index, u32 cbuf_index, u32 cbuf_offset,
bool BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index, u32 cbuf_index, u32 cbuf_offset,
bool is_written);
void UnbindGraphicsTextureBuffers(size_t stage);

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -128,7 +131,7 @@ bool Passes(const std::array<Shader::Info, 5>& stage_infos, u32 enabled_mask) {
return true;
}
using ConfigureFuncPtr = void (*)(GraphicsPipeline*, bool);
using ConfigureFuncPtr = bool (*)(GraphicsPipeline*, bool);
template <typename Spec, typename... Specs>
ConfigureFuncPtr FindSpec(const std::array<Shader::Info, 5>& stage_infos, u32 enabled_mask) {
@ -275,7 +278,7 @@ GraphicsPipeline::GraphicsPipeline(const Device& device, TextureCache& texture_c
}
template <typename Spec>
void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
std::array<VideoCommon::ImageViewInOut, MAX_TEXTURES + MAX_IMAGES> views;
std::array<VideoCommon::SamplerId, MAX_TEXTURES> samplers;
size_t views_index{};
@ -556,6 +559,8 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
if (image_binding != 0) {
glBindImageTextures(0, image_binding, images.data());
}
return true;
}
void GraphicsPipeline::ConfigureTransformFeedbackImpl() const {

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -80,8 +83,8 @@ public:
const std::array<const Shader::Info*, 5>& infos,
const GraphicsPipelineKey& key_, bool force_context_flush = false);
void Configure(bool is_indexed) {
configure_func(this, is_indexed);
bool Configure(bool is_indexed) {
return configure_func(this, is_indexed);
}
void ConfigureTransformFeedback() const {
@ -107,7 +110,7 @@ public:
template <typename Spec>
static auto MakeConfigureSpecFunc() {
return [](GraphicsPipeline* pipeline, bool is_indexed) {
pipeline->ConfigureImpl<Spec>(is_indexed);
return pipeline->ConfigureImpl<Spec>(is_indexed);
};
}
@ -118,7 +121,7 @@ public:
private:
template <typename Spec>
void ConfigureImpl(bool is_indexed);
bool ConfigureImpl(bool is_indexed);
void ConfigureTransformFeedbackImpl() const;
@ -134,7 +137,7 @@ private:
StateTracker& state_tracker;
const GraphicsPipelineKey key;
void (*configure_func)(GraphicsPipeline*, bool){};
bool (*configure_func)(GraphicsPipeline*, bool){};
std::array<OGLProgram, 5> source_programs;
std::array<OGLAssemblyProgram, 5> assembly_programs;

View file

@ -250,7 +250,8 @@ void RasterizerOpenGL::PrepareDraw(bool is_indexed, Func&& draw_func) {
program_manager.LocalMemoryWarmup();
}
pipeline->SetEngine(maxwell3d, gpu_memory);
pipeline->Configure(is_indexed);
if (!pipeline->Configure(is_indexed))
return;
SyncState();

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -175,7 +178,7 @@ bool Passes(const std::array<vk::ShaderModule, NUM_STAGES>& modules,
return true;
}
using ConfigureFuncPtr = void (*)(GraphicsPipeline*, bool);
using ConfigureFuncPtr = bool (*)(GraphicsPipeline*, bool);
template <typename Spec, typename... Specs>
ConfigureFuncPtr FindSpec(const std::array<vk::ShaderModule, NUM_STAGES>& modules,
@ -302,7 +305,7 @@ void GraphicsPipeline::AddTransition(GraphicsPipeline* transition) {
}
template <typename Spec>
void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
std::array<VideoCommon::ImageViewInOut, MAX_IMAGE_ELEMENTS> views;
std::array<VideoCommon::SamplerId, MAX_IMAGE_ELEMENTS> samplers;
size_t sampler_index{};
@ -321,8 +324,9 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
size_t ssbo_index{};
for (const auto& desc : info.storage_buffers_descriptors) {
ASSERT(desc.count == 1);
buffer_cache.BindGraphicsStorageBuffer(stage, ssbo_index, desc.cbuf_index,
desc.cbuf_offset, desc.is_written);
if (!buffer_cache.BindGraphicsStorageBuffer(stage, ssbo_index, desc.cbuf_index,
desc.cbuf_offset, desc.is_written))
return false;
++ssbo_index;
}
}
@ -382,6 +386,8 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
add_image(desc, desc.is_written);
}
}
return true;
}};
if constexpr (Spec::enabled_stages[0]) {
config_stage(0);
@ -396,7 +402,8 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
config_stage(3);
}
if constexpr (Spec::enabled_stages[4]) {
config_stage(4);
if (!config_stage(4))
return false;
}
texture_cache.FillGraphicsImageViews<Spec::has_images>(std::span(views.data(), view_index));
@ -490,6 +497,8 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
texture_cache.UpdateRenderTargets(false);
texture_cache.CheckFeedbackLoop(views);
ConfigureDraw(rescaling, render_area);
return true;
}
void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling,

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -86,8 +89,8 @@ public:
void AddTransition(GraphicsPipeline* transition);
void Configure(bool is_indexed) {
configure_func(this, is_indexed);
bool Configure(bool is_indexed) {
return configure_func(this, is_indexed);
}
[[nodiscard]] GraphicsPipeline* Next(const GraphicsPipelineCacheKey& current_key) noexcept {
@ -105,7 +108,7 @@ public:
template <typename Spec>
static auto MakeConfigureSpecFunc() {
return [](GraphicsPipeline* pl, bool is_indexed) { pl->ConfigureImpl<Spec>(is_indexed); };
return [](GraphicsPipeline* pl, bool is_indexed) { return pl->ConfigureImpl<Spec>(is_indexed); };
}
void SetEngine(Tegra::Engines::Maxwell3D* maxwell3d_, Tegra::MemoryManager* gpu_memory_) {
@ -115,7 +118,7 @@ public:
private:
template <typename Spec>
void ConfigureImpl(bool is_indexed);
bool ConfigureImpl(bool is_indexed);
void ConfigureDraw(const RescalingPushConstant& rescaling,
const RenderAreaPushConstant& render_are);
@ -134,7 +137,7 @@ private:
Scheduler& scheduler;
GuestDescriptorQueue& guest_descriptor_queue;
void (*configure_func)(GraphicsPipeline*, bool){};
bool (*configure_func)(GraphicsPipeline*, bool){};
std::vector<GraphicsPipelineCacheKey> transition_keys;
std::vector<GraphicsPipeline*> transitions;

View file

@ -226,7 +226,8 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) {
std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
// update engine as channel may be different.
pipeline->SetEngine(maxwell3d, gpu_memory);
pipeline->Configure(is_indexed);
if (!pipeline->Configure(is_indexed))
return;
UpdateDynamicStates();