[shader_recompiler, renderer_vulkan] draw sprites whose texture handle comes from a vertex attribute (#4566)

- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md).
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------
credit: Ghael-V

Draw sprites whose texture handle comes from a vertex attribute. A bindless handle read from an interpolated vertex attribute could not be tracked to a constant buffer, so pipeline creation threw and every draw using the shader was dropped (FIFA player indicators, shadows and some text).

The handle now gets a sentinel descriptor, and the Vulkan rasterizer splits a triangle-list draw into runs of vertices that share a handle and binds each run's texture before drawing it.

4fec247e9e
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4566
Reviewed-by: Shinmegumi <shinmegumi@eden-emu.dev>
This commit is contained in:
PavelBARABANOV 2026-11-01 21:44:40 +01:00 • committed by crueter
parent be21ccce25
commit 3d3ed4b7d1
No known key found for this signature in database
GPG key ID: 425ACD2D4830EBC6
8 changed files with 189 additions and 12 deletions

View file

@ -40,6 +40,8 @@ struct HostTranslateInfo {
bool support_conditional_barrier{}; ///< True when the device supports barriers in conditional
///< control flow
bool single_lane_geometry_subgroups{};
bool support_attribute_texture_handle{}; ///< True when the host binds texture handles that
///< arrive as vertex attributes
void ApplyDescriptorLimitPolicy() noexcept {
if (min_ssbo_alignment == 0) {

View file

@ -322,6 +322,10 @@ static inline u32 ReadCbufCached(Environment& env, u32 index, u32 offset) {
return v;
}
static inline bool IsAttributeHandle(const ConstBufferAddr& cbuf) {
return cbuf.index == ATTRIBUTE_HANDLE_CBUF_INDEX;
}
static inline u32 GetTextureHandleCached(Environment& env, const ConstBufferAddr& cbuf) {
// Must all be uniquely different variables
// If has secondary, then it will be cbuf.secondary_{index|offset}, else its 0.
@ -341,14 +345,23 @@ static inline u32 GetTextureHandleCached(Environment& env, const ConstBufferAddr
// Cached variants of existing helpers
static inline TextureType ReadTextureTypeCached(Environment& env, const ConstBufferAddr& cbuf) {
if (IsAttributeHandle(cbuf)) {
return TextureType::Color2D;
}
return env.ReadTextureType(GetTextureHandleCached(env, cbuf));
}
static inline TexturePixelFormat ReadTexturePixelFormatCached(Environment& env,
const ConstBufferAddr& cbuf) {
if (IsAttributeHandle(cbuf)) {
return TexturePixelFormat::A8B8G8R8_UNORM;
}
return env.ReadTexturePixelFormat(GetTextureHandleCached(env, cbuf));
}
static inline bool IsTexturePixelFormatIntegerCached(Environment& env,
const ConstBufferAddr& cbuf) {
if (IsAttributeHandle(cbuf)) {
return false;
}
return env.IsTexturePixelFormatInteger(GetTextureHandleCached(env, cbuf));
}
@ -520,15 +533,43 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environme
};
}
bool IsAttributeSourcedHandle(const IR::Value& handle) {
if (handle.IsImmediate()) {
return false;
}
const IR::Inst* inst{handle.InstRecursive()};
if (inst->GetOpcode() == IR::Opcode::BitCastU32F32 && !inst->Arg(0).IsImmediate()) {
inst = inst->Arg(0).InstRecursive();
}
return inst->GetOpcode() == IR::Opcode::GetAttribute ||
inst->GetOpcode() == IR::Opcode::GetAttributeU32;
}
TextureInst MakeInst(Environment& env, IR::Block* block, IR::Inst& inst, const HostTranslateInfo& host_info) {
ConstBufferAddr addr;
if (IsBindless(inst)) {
const std::optional<ConstBufferAddr> track_addr{TrackCached(inst.Arg(0), env, host_info, block)};
if (!track_addr) {
throw NotImplementedException("Failed to track bindless texture constant buffer");
} else {
if (track_addr) {
addr = *track_addr;
} else if (host_info.support_attribute_texture_handle &&
IsAttributeSourcedHandle(inst.Arg(0))) {
// The handle travels with the vertices (sprite batches pick the texture per quad), so
// there is no constant buffer to read it from. The host binds the handle of each run
// of vertices that share one.
addr = ConstBufferAddr{
.index = ATTRIBUTE_HANDLE_CBUF_INDEX,
.offset = 0,
.shift_left = 0,
.secondary_index = 0,
.secondary_offset = 0,
.secondary_shift_left = 0,
.dynamic_offset = {},
.count = 1,
.has_secondary = false,
};
} else {
throw NotImplementedException("Failed to track bindless texture constant buffer");
}
} else {
addr = ConstBufferAddr{

View file

@ -38,6 +38,10 @@ enum class TextureType : u32 {
};
constexpr u32 NUM_TEXTURE_TYPES = 9;
/// Constant buffer index marking a texture handle that the shader receives as a vertex attribute
/// instead of reading it from a constant buffer. The host supplies the handle of each draw run.
constexpr u32 ATTRIBUTE_HANDLE_CBUF_INDEX = 0xFFFF;
enum class TexturePixelFormat {
A8B8G8R8_UNORM,
A8B8G8R8_SNORM,