[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> 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) { u32 cbuf_offset, bool is_written) {
channel_state->enabled_storage_buffers[stage] |= 1U << ssbo_index; channel_state->enabled_storage_buffers[stage] |= 1U << ssbo_index;
channel_state->written_storage_buffers[stage] |= (is_written ? 1U : 0U) << 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; const GPUVAddr ssbo_addr = cbufs.const_buffers[cbuf_index].address + cbuf_offset;
channel_state->storage_buffers[stage][ssbo_index] = channel_state->storage_buffers[stage][ssbo_index] =
StorageBufferBinding(ssbo_addr, cbuf_index, is_written); StorageBufferBinding(ssbo_addr, cbuf_index, is_written);
return (channel_state->storage_buffers[stage][ssbo_index].buffer_id != NULL_BUFFER_ID);
} }
template <class P> template <class P>

View file

@ -235,7 +235,7 @@ public:
void UnbindGraphicsStorageBuffers(size_t stage); 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); bool is_written);
void UnbindGraphicsTextureBuffers(size_t stage); 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-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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; return true;
} }
using ConfigureFuncPtr = void (*)(GraphicsPipeline*, bool); using ConfigureFuncPtr = bool (*)(GraphicsPipeline*, bool);
template <typename Spec, typename... Specs> template <typename Spec, typename... Specs>
ConfigureFuncPtr FindSpec(const std::array<Shader::Info, 5>& stage_infos, u32 enabled_mask) { 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> 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::ImageViewInOut, MAX_TEXTURES + MAX_IMAGES> views;
std::array<VideoCommon::SamplerId, MAX_TEXTURES> samplers; std::array<VideoCommon::SamplerId, MAX_TEXTURES> samplers;
size_t views_index{}; size_t views_index{};
@ -556,6 +559,8 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
if (image_binding != 0) { if (image_binding != 0) {
glBindImageTextures(0, image_binding, images.data()); glBindImageTextures(0, image_binding, images.data());
} }
return true;
} }
void GraphicsPipeline::ConfigureTransformFeedbackImpl() const { 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-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -80,8 +83,8 @@ public:
const std::array<const Shader::Info*, 5>& infos, const std::array<const Shader::Info*, 5>& infos,
const GraphicsPipelineKey& key_, bool force_context_flush = false); const GraphicsPipelineKey& key_, bool force_context_flush = false);
void Configure(bool is_indexed) { bool Configure(bool is_indexed) {
configure_func(this, is_indexed); return configure_func(this, is_indexed);
} }
void ConfigureTransformFeedback() const { void ConfigureTransformFeedback() const {
@ -107,7 +110,7 @@ public:
template <typename Spec> template <typename Spec>
static auto MakeConfigureSpecFunc() { static auto MakeConfigureSpecFunc() {
return [](GraphicsPipeline* pipeline, bool is_indexed) { return [](GraphicsPipeline* pipeline, bool is_indexed) {
pipeline->ConfigureImpl<Spec>(is_indexed); return pipeline->ConfigureImpl<Spec>(is_indexed);
}; };
} }
@ -118,7 +121,7 @@ public:
private: private:
template <typename Spec> template <typename Spec>
void ConfigureImpl(bool is_indexed); bool ConfigureImpl(bool is_indexed);
void ConfigureTransformFeedbackImpl() const; void ConfigureTransformFeedbackImpl() const;
@ -134,7 +137,7 @@ private:
StateTracker& state_tracker; StateTracker& state_tracker;
const GraphicsPipelineKey key; const GraphicsPipelineKey key;
void (*configure_func)(GraphicsPipeline*, bool){}; bool (*configure_func)(GraphicsPipeline*, bool){};
std::array<OGLProgram, 5> source_programs; std::array<OGLProgram, 5> source_programs;
std::array<OGLAssemblyProgram, 5> assembly_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(); program_manager.LocalMemoryWarmup();
} }
pipeline->SetEngine(maxwell3d, gpu_memory); pipeline->SetEngine(maxwell3d, gpu_memory);
pipeline->Configure(is_indexed); if (!pipeline->Configure(is_indexed))
return;
SyncState(); 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-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -175,7 +178,7 @@ bool Passes(const std::array<vk::ShaderModule, NUM_STAGES>& modules,
return true; return true;
} }
using ConfigureFuncPtr = void (*)(GraphicsPipeline*, bool); using ConfigureFuncPtr = bool (*)(GraphicsPipeline*, bool);
template <typename Spec, typename... Specs> template <typename Spec, typename... Specs>
ConfigureFuncPtr FindSpec(const std::array<vk::ShaderModule, NUM_STAGES>& modules, ConfigureFuncPtr FindSpec(const std::array<vk::ShaderModule, NUM_STAGES>& modules,
@ -302,7 +305,7 @@ void GraphicsPipeline::AddTransition(GraphicsPipeline* transition) {
} }
template <typename Spec> 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::ImageViewInOut, MAX_IMAGE_ELEMENTS> views;
std::array<VideoCommon::SamplerId, MAX_IMAGE_ELEMENTS> samplers; std::array<VideoCommon::SamplerId, MAX_IMAGE_ELEMENTS> samplers;
size_t sampler_index{}; size_t sampler_index{};
@ -321,8 +324,9 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
size_t ssbo_index{}; size_t ssbo_index{};
for (const auto& desc : info.storage_buffers_descriptors) { for (const auto& desc : info.storage_buffers_descriptors) {
ASSERT(desc.count == 1); ASSERT(desc.count == 1);
buffer_cache.BindGraphicsStorageBuffer(stage, ssbo_index, desc.cbuf_index, if (!buffer_cache.BindGraphicsStorageBuffer(stage, ssbo_index, desc.cbuf_index,
desc.cbuf_offset, desc.is_written); desc.cbuf_offset, desc.is_written))
return false;
++ssbo_index; ++ssbo_index;
} }
} }
@ -382,6 +386,8 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
add_image(desc, desc.is_written); add_image(desc, desc.is_written);
} }
} }
return true;
}}; }};
if constexpr (Spec::enabled_stages[0]) { if constexpr (Spec::enabled_stages[0]) {
config_stage(0); config_stage(0);
@ -396,7 +402,8 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
config_stage(3); config_stage(3);
} }
if constexpr (Spec::enabled_stages[4]) { 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)); 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.UpdateRenderTargets(false);
texture_cache.CheckFeedbackLoop(views); texture_cache.CheckFeedbackLoop(views);
ConfigureDraw(rescaling, render_area); ConfigureDraw(rescaling, render_area);
return true;
} }
void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling, 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-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -86,8 +89,8 @@ public:
void AddTransition(GraphicsPipeline* transition); void AddTransition(GraphicsPipeline* transition);
void Configure(bool is_indexed) { bool Configure(bool is_indexed) {
configure_func(this, is_indexed); return configure_func(this, is_indexed);
} }
[[nodiscard]] GraphicsPipeline* Next(const GraphicsPipelineCacheKey& current_key) noexcept { [[nodiscard]] GraphicsPipeline* Next(const GraphicsPipelineCacheKey& current_key) noexcept {
@ -105,7 +108,7 @@ public:
template <typename Spec> template <typename Spec>
static auto MakeConfigureSpecFunc() { 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_) { void SetEngine(Tegra::Engines::Maxwell3D* maxwell3d_, Tegra::MemoryManager* gpu_memory_) {
@ -115,7 +118,7 @@ public:
private: private:
template <typename Spec> template <typename Spec>
void ConfigureImpl(bool is_indexed); bool ConfigureImpl(bool is_indexed);
void ConfigureDraw(const RescalingPushConstant& rescaling, void ConfigureDraw(const RescalingPushConstant& rescaling,
const RenderAreaPushConstant& render_are); const RenderAreaPushConstant& render_are);
@ -134,7 +137,7 @@ private:
Scheduler& scheduler; Scheduler& scheduler;
GuestDescriptorQueue& guest_descriptor_queue; GuestDescriptorQueue& guest_descriptor_queue;
void (*configure_func)(GraphicsPipeline*, bool){}; bool (*configure_func)(GraphicsPipeline*, bool){};
std::vector<GraphicsPipelineCacheKey> transition_keys; std::vector<GraphicsPipelineCacheKey> transition_keys;
std::vector<GraphicsPipeline*> transitions; 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}; std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
// update engine as channel may be different. // update engine as channel may be different.
pipeline->SetEngine(maxwell3d, gpu_memory); pipeline->SetEngine(maxwell3d, gpu_memory);
pipeline->Configure(is_indexed); if (!pipeline->Configure(is_indexed))
return;
UpdateDynamicStates(); UpdateDynamicStates();