Implement scaled vertex buffer format emulation
These formats are unsupported by mobile GPUs so they need to be emulated in shaders instead.
This commit is contained in:
parent
206f1304d6
commit
158a1896ec
|
@ -10,27 +10,6 @@
|
||||||
|
|
||||||
namespace Shader::Backend::SPIRV {
|
namespace Shader::Backend::SPIRV {
|
||||||
namespace {
|
namespace {
|
||||||
struct AttrInfo {
|
|
||||||
Id pointer;
|
|
||||||
Id id;
|
|
||||||
bool needs_cast;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::optional<AttrInfo> AttrTypes(EmitContext& ctx, u32 index) {
|
|
||||||
const AttributeType type{ctx.runtime_info.generic_input_types.at(index)};
|
|
||||||
switch (type) {
|
|
||||||
case AttributeType::Float:
|
|
||||||
return AttrInfo{ctx.input_f32, ctx.F32[1], false};
|
|
||||||
case AttributeType::UnsignedInt:
|
|
||||||
return AttrInfo{ctx.input_u32, ctx.U32[1], true};
|
|
||||||
case AttributeType::SignedInt:
|
|
||||||
return AttrInfo{ctx.input_s32, ctx.TypeInt(32, true), true};
|
|
||||||
case AttributeType::Disabled:
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
throw InvalidArgument("Invalid attribute type {}", type);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
Id AttrPointer(EmitContext& ctx, Id pointer_type, Id vertex, Id base, Args&&... args) {
|
Id AttrPointer(EmitContext& ctx, Id pointer_type, Id vertex, Id base, Args&&... args) {
|
||||||
switch (ctx.stage) {
|
switch (ctx.stage) {
|
||||||
|
@ -302,15 +281,26 @@ Id EmitGetAttribute(EmitContext& ctx, IR::Attribute attr, Id vertex) {
|
||||||
const u32 element{static_cast<u32>(attr) % 4};
|
const u32 element{static_cast<u32>(attr) % 4};
|
||||||
if (IR::IsGeneric(attr)) {
|
if (IR::IsGeneric(attr)) {
|
||||||
const u32 index{IR::GenericAttributeIndex(attr)};
|
const u32 index{IR::GenericAttributeIndex(attr)};
|
||||||
const std::optional<AttrInfo> type{AttrTypes(ctx, index)};
|
const auto& generic{ctx.input_generics.at(index)};
|
||||||
if (!type || !ctx.runtime_info.previous_stage_stores.Generic(index, element)) {
|
if (!ValidId(generic.id)) {
|
||||||
// Attribute is disabled or varying component is not written
|
// Attribute is disabled or varying component is not written
|
||||||
return ctx.Const(element == 3 ? 1.0f : 0.0f);
|
return ctx.Const(element == 3 ? 1.0f : 0.0f);
|
||||||
}
|
}
|
||||||
const Id generic_id{ctx.input_generics.at(index)};
|
const Id pointer{
|
||||||
const Id pointer{AttrPointer(ctx, type->pointer, vertex, generic_id, ctx.Const(element))};
|
AttrPointer(ctx, generic.pointer_type, vertex, generic.id, ctx.Const(element))};
|
||||||
const Id value{ctx.OpLoad(type->id, pointer)};
|
const Id value{ctx.OpLoad(generic.component_type, pointer)};
|
||||||
return type->needs_cast ? ctx.OpBitcast(ctx.F32[1], value) : value;
|
return [&ctx, generic, value]() {
|
||||||
|
switch (generic.load_op) {
|
||||||
|
case InputGenericLoadOp::Bitcast:
|
||||||
|
return ctx.OpBitcast(ctx.F32[1], value);
|
||||||
|
case InputGenericLoadOp::SToF:
|
||||||
|
return ctx.OpConvertSToF(ctx.F32[1], value);
|
||||||
|
case InputGenericLoadOp::UToF:
|
||||||
|
return ctx.OpConvertUToF(ctx.F32[1], value);
|
||||||
|
default:
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
}();
|
||||||
}
|
}
|
||||||
switch (attr) {
|
switch (attr) {
|
||||||
case IR::Attribute::PrimitiveId:
|
case IR::Attribute::PrimitiveId:
|
||||||
|
|
|
@ -25,12 +25,6 @@ enum class Operation {
|
||||||
FPMax,
|
FPMax,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AttrInfo {
|
|
||||||
Id pointer;
|
|
||||||
Id id;
|
|
||||||
bool needs_cast;
|
|
||||||
};
|
|
||||||
|
|
||||||
Id ImageType(EmitContext& ctx, const TextureDescriptor& desc) {
|
Id ImageType(EmitContext& ctx, const TextureDescriptor& desc) {
|
||||||
const spv::ImageFormat format{spv::ImageFormat::Unknown};
|
const spv::ImageFormat format{spv::ImageFormat::Unknown};
|
||||||
const Id type{ctx.F32[1]};
|
const Id type{ctx.F32[1]};
|
||||||
|
@ -206,23 +200,37 @@ Id GetAttributeType(EmitContext& ctx, AttributeType type) {
|
||||||
return ctx.TypeVector(ctx.TypeInt(32, true), 4);
|
return ctx.TypeVector(ctx.TypeInt(32, true), 4);
|
||||||
case AttributeType::UnsignedInt:
|
case AttributeType::UnsignedInt:
|
||||||
return ctx.U32[4];
|
return ctx.U32[4];
|
||||||
|
case AttributeType::SignedScaled:
|
||||||
|
return ctx.profile.support_scaled_attributes ? ctx.F32[4]
|
||||||
|
: ctx.TypeVector(ctx.TypeInt(32, true), 4);
|
||||||
|
case AttributeType::UnsignedScaled:
|
||||||
|
return ctx.profile.support_scaled_attributes ? ctx.F32[4] : ctx.U32[4];
|
||||||
case AttributeType::Disabled:
|
case AttributeType::Disabled:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
throw InvalidArgument("Invalid attribute type {}", type);
|
throw InvalidArgument("Invalid attribute type {}", type);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<AttrInfo> AttrTypes(EmitContext& ctx, u32 index) {
|
InputGenericInfo GetAttributeInfo(EmitContext& ctx, AttributeType type, Id id) {
|
||||||
const AttributeType type{ctx.runtime_info.generic_input_types.at(index)};
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case AttributeType::Float:
|
case AttributeType::Float:
|
||||||
return AttrInfo{ctx.input_f32, ctx.F32[1], false};
|
return InputGenericInfo{id, ctx.input_f32, ctx.F32[1], InputGenericLoadOp::None};
|
||||||
case AttributeType::UnsignedInt:
|
case AttributeType::UnsignedInt:
|
||||||
return AttrInfo{ctx.input_u32, ctx.U32[1], true};
|
return InputGenericInfo{id, ctx.input_u32, ctx.U32[1], InputGenericLoadOp::Bitcast};
|
||||||
case AttributeType::SignedInt:
|
case AttributeType::SignedInt:
|
||||||
return AttrInfo{ctx.input_s32, ctx.TypeInt(32, true), true};
|
return InputGenericInfo{id, ctx.input_s32, ctx.TypeInt(32, true),
|
||||||
|
InputGenericLoadOp::Bitcast};
|
||||||
|
case AttributeType::SignedScaled:
|
||||||
|
return ctx.profile.support_scaled_attributes
|
||||||
|
? InputGenericInfo{id, ctx.input_f32, ctx.F32[1], InputGenericLoadOp::None}
|
||||||
|
: InputGenericInfo{id, ctx.input_s32, ctx.TypeInt(32, true),
|
||||||
|
InputGenericLoadOp::SToF};
|
||||||
|
case AttributeType::UnsignedScaled:
|
||||||
|
return ctx.profile.support_scaled_attributes
|
||||||
|
? InputGenericInfo{id, ctx.input_f32, ctx.F32[1], InputGenericLoadOp::None}
|
||||||
|
: InputGenericInfo{id, ctx.input_u32, ctx.U32[1], InputGenericLoadOp::UToF};
|
||||||
case AttributeType::Disabled:
|
case AttributeType::Disabled:
|
||||||
return std::nullopt;
|
return InputGenericInfo{};
|
||||||
}
|
}
|
||||||
throw InvalidArgument("Invalid attribute type {}", type);
|
throw InvalidArgument("Invalid attribute type {}", type);
|
||||||
}
|
}
|
||||||
|
@ -746,18 +754,29 @@ void EmitContext::DefineAttributeMemAccess(const Info& info) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
AddLabel(labels[label_index]);
|
AddLabel(labels[label_index]);
|
||||||
const auto type{AttrTypes(*this, static_cast<u32>(index))};
|
const auto& generic{input_generics.at(index)};
|
||||||
if (!type) {
|
const Id generic_id{generic.id};
|
||||||
|
if (!ValidId(generic_id)) {
|
||||||
OpReturnValue(Const(0.0f));
|
OpReturnValue(Const(0.0f));
|
||||||
++label_index;
|
++label_index;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const Id generic_id{input_generics.at(index)};
|
const Id pointer{
|
||||||
const Id pointer{is_array
|
is_array ? OpAccessChain(generic.pointer_type, generic_id, vertex, masked_index)
|
||||||
? OpAccessChain(type->pointer, generic_id, vertex, masked_index)
|
: OpAccessChain(generic.pointer_type, generic_id, masked_index)};
|
||||||
: OpAccessChain(type->pointer, generic_id, masked_index)};
|
const Id value{OpLoad(generic.component_type, pointer)};
|
||||||
const Id value{OpLoad(type->id, pointer)};
|
const Id result{[this, generic, value]() {
|
||||||
const Id result{type->needs_cast ? OpBitcast(F32[1], value) : value};
|
switch (generic.load_op) {
|
||||||
|
case InputGenericLoadOp::Bitcast:
|
||||||
|
return OpBitcast(F32[1], value);
|
||||||
|
case InputGenericLoadOp::SToF:
|
||||||
|
return OpConvertSToF(F32[1], value);
|
||||||
|
case InputGenericLoadOp::UToF:
|
||||||
|
return OpConvertUToF(F32[1], value);
|
||||||
|
default:
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
}()};
|
||||||
OpReturnValue(result);
|
OpReturnValue(result);
|
||||||
++label_index;
|
++label_index;
|
||||||
}
|
}
|
||||||
|
@ -1457,7 +1476,7 @@ void EmitContext::DefineInputs(const IR::Program& program) {
|
||||||
const Id id{DefineInput(*this, type, true)};
|
const Id id{DefineInput(*this, type, true)};
|
||||||
Decorate(id, spv::Decoration::Location, static_cast<u32>(index));
|
Decorate(id, spv::Decoration::Location, static_cast<u32>(index));
|
||||||
Name(id, fmt::format("in_attr{}", index));
|
Name(id, fmt::format("in_attr{}", index));
|
||||||
input_generics[index] = id;
|
input_generics[index] = GetAttributeInfo(*this, input_type, id);
|
||||||
|
|
||||||
if (info.passthrough.Generic(index) && profile.support_geometry_shader_passthrough) {
|
if (info.passthrough.Generic(index) && profile.support_geometry_shader_passthrough) {
|
||||||
Decorate(id, spv::Decoration::PassthroughNV);
|
Decorate(id, spv::Decoration::PassthroughNV);
|
||||||
|
|
|
@ -95,6 +95,20 @@ struct StorageDefinitions {
|
||||||
Id U32x4{};
|
Id U32x4{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class InputGenericLoadOp {
|
||||||
|
None,
|
||||||
|
Bitcast,
|
||||||
|
SToF,
|
||||||
|
UToF,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct InputGenericInfo {
|
||||||
|
Id id;
|
||||||
|
Id pointer_type;
|
||||||
|
Id component_type;
|
||||||
|
InputGenericLoadOp load_op;
|
||||||
|
};
|
||||||
|
|
||||||
struct GenericElementInfo {
|
struct GenericElementInfo {
|
||||||
Id id{};
|
Id id{};
|
||||||
u32 first_element{};
|
u32 first_element{};
|
||||||
|
@ -283,7 +297,7 @@ public:
|
||||||
|
|
||||||
bool need_input_position_indirect{};
|
bool need_input_position_indirect{};
|
||||||
Id input_position{};
|
Id input_position{};
|
||||||
std::array<Id, 32> input_generics{};
|
std::array<InputGenericInfo, 32> input_generics{};
|
||||||
|
|
||||||
Id output_point_size{};
|
Id output_point_size{};
|
||||||
Id output_position{};
|
Id output_position{};
|
||||||
|
|
|
@ -43,6 +43,7 @@ struct Profile {
|
||||||
bool support_gl_variable_aoffi{};
|
bool support_gl_variable_aoffi{};
|
||||||
bool support_gl_sparse_textures{};
|
bool support_gl_sparse_textures{};
|
||||||
bool support_gl_derivative_control{};
|
bool support_gl_derivative_control{};
|
||||||
|
bool support_scaled_attributes{};
|
||||||
|
|
||||||
bool warp_size_potentially_larger_than_guest{};
|
bool warp_size_potentially_larger_than_guest{};
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,8 @@ enum class AttributeType : u8 {
|
||||||
Float,
|
Float,
|
||||||
SignedInt,
|
SignedInt,
|
||||||
UnsignedInt,
|
UnsignedInt,
|
||||||
|
SignedScaled,
|
||||||
|
UnsignedScaled,
|
||||||
Disabled,
|
Disabled,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -347,6 +347,14 @@ VkPrimitiveTopology PrimitiveTopology([[maybe_unused]] const Device& device,
|
||||||
|
|
||||||
VkFormat VertexFormat(const Device& device, Maxwell::VertexAttribute::Type type,
|
VkFormat VertexFormat(const Device& device, Maxwell::VertexAttribute::Type type,
|
||||||
Maxwell::VertexAttribute::Size size) {
|
Maxwell::VertexAttribute::Size size) {
|
||||||
|
if (device.MustEmulateScaledFormats()) {
|
||||||
|
if (type == Maxwell::VertexAttribute::Type::SScaled) {
|
||||||
|
type = Maxwell::VertexAttribute::Type::SInt;
|
||||||
|
} else if (type == Maxwell::VertexAttribute::Type::UScaled) {
|
||||||
|
type = Maxwell::VertexAttribute::Type::UInt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const VkFormat format{([&]() {
|
const VkFormat format{([&]() {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case Maxwell::VertexAttribute::Type::UnusedEnumDoNotUseBecauseItWillGoAway:
|
case Maxwell::VertexAttribute::Type::UnusedEnumDoNotUseBecauseItWillGoAway:
|
||||||
|
|
|
@ -114,14 +114,16 @@ Shader::AttributeType CastAttributeType(const FixedPipelineState::VertexAttribut
|
||||||
return Shader::AttributeType::Disabled;
|
return Shader::AttributeType::Disabled;
|
||||||
case Maxwell::VertexAttribute::Type::SNorm:
|
case Maxwell::VertexAttribute::Type::SNorm:
|
||||||
case Maxwell::VertexAttribute::Type::UNorm:
|
case Maxwell::VertexAttribute::Type::UNorm:
|
||||||
case Maxwell::VertexAttribute::Type::UScaled:
|
|
||||||
case Maxwell::VertexAttribute::Type::SScaled:
|
|
||||||
case Maxwell::VertexAttribute::Type::Float:
|
case Maxwell::VertexAttribute::Type::Float:
|
||||||
return Shader::AttributeType::Float;
|
return Shader::AttributeType::Float;
|
||||||
case Maxwell::VertexAttribute::Type::SInt:
|
case Maxwell::VertexAttribute::Type::SInt:
|
||||||
return Shader::AttributeType::SignedInt;
|
return Shader::AttributeType::SignedInt;
|
||||||
case Maxwell::VertexAttribute::Type::UInt:
|
case Maxwell::VertexAttribute::Type::UInt:
|
||||||
return Shader::AttributeType::UnsignedInt;
|
return Shader::AttributeType::UnsignedInt;
|
||||||
|
case Maxwell::VertexAttribute::Type::UScaled:
|
||||||
|
return Shader::AttributeType::UnsignedScaled;
|
||||||
|
case Maxwell::VertexAttribute::Type::SScaled:
|
||||||
|
return Shader::AttributeType::SignedScaled;
|
||||||
}
|
}
|
||||||
return Shader::AttributeType::Float;
|
return Shader::AttributeType::Float;
|
||||||
}
|
}
|
||||||
|
@ -331,6 +333,7 @@ PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device
|
||||||
.support_derivative_control = true,
|
.support_derivative_control = true,
|
||||||
.support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(),
|
.support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(),
|
||||||
.support_native_ndc = device.IsExtDepthClipControlSupported(),
|
.support_native_ndc = device.IsExtDepthClipControlSupported(),
|
||||||
|
.support_scaled_attributes = !device.MustEmulateScaledFormats(),
|
||||||
|
|
||||||
.warp_size_potentially_larger_than_guest = device.IsWarpSizePotentiallyBiggerThanGuest(),
|
.warp_size_potentially_larger_than_guest = device.IsWarpSizePotentiallyBiggerThanGuest(),
|
||||||
|
|
||||||
|
|
|
@ -363,6 +363,8 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||||
|
|
||||||
#ifdef ANDROID
|
#ifdef ANDROID
|
||||||
if (is_adreno) {
|
if (is_adreno) {
|
||||||
|
must_emulate_scaled_formats = true;
|
||||||
|
|
||||||
LOG_WARNING(Render_Vulkan, "Adreno drivers have broken VK_EXT_extended_dynamic_state");
|
LOG_WARNING(Render_Vulkan, "Adreno drivers have broken VK_EXT_extended_dynamic_state");
|
||||||
extensions.extended_dynamic_state = false;
|
extensions.extended_dynamic_state = false;
|
||||||
loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
|
loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
|
||||||
|
@ -391,6 +393,8 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_arm) {
|
if (is_arm) {
|
||||||
|
must_emulate_scaled_formats = true;
|
||||||
|
|
||||||
LOG_WARNING(Render_Vulkan, "ARM drivers have broken VK_EXT_extended_dynamic_state");
|
LOG_WARNING(Render_Vulkan, "ARM drivers have broken VK_EXT_extended_dynamic_state");
|
||||||
extensions.extended_dynamic_state = false;
|
extensions.extended_dynamic_state = false;
|
||||||
loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
|
loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
|
||||||
|
|
|
@ -551,6 +551,10 @@ public:
|
||||||
return cant_blit_msaa;
|
return cant_blit_msaa;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MustEmulateScaledFormats() const {
|
||||||
|
return must_emulate_scaled_formats;
|
||||||
|
}
|
||||||
|
|
||||||
bool MustEmulateBGR565() const {
|
bool MustEmulateBGR565() const {
|
||||||
return must_emulate_bgr565;
|
return must_emulate_bgr565;
|
||||||
}
|
}
|
||||||
|
@ -666,6 +670,7 @@ private:
|
||||||
bool has_nsight_graphics{}; ///< Has Nsight Graphics attached
|
bool has_nsight_graphics{}; ///< Has Nsight Graphics attached
|
||||||
bool supports_d24_depth{}; ///< Supports D24 depth buffers.
|
bool supports_d24_depth{}; ///< Supports D24 depth buffers.
|
||||||
bool cant_blit_msaa{}; ///< Does not support MSAA<->MSAA blitting.
|
bool cant_blit_msaa{}; ///< Does not support MSAA<->MSAA blitting.
|
||||||
|
bool must_emulate_scaled_formats{}; ///< Requires scaled vertex format emulation
|
||||||
bool must_emulate_bgr565{}; ///< Emulates BGR565 by swizzling RGB565 format.
|
bool must_emulate_bgr565{}; ///< Emulates BGR565 by swizzling RGB565 format.
|
||||||
bool dynamic_state3_blending{}; ///< Has all blending features of dynamic_state3.
|
bool dynamic_state3_blending{}; ///< Has all blending features of dynamic_state3.
|
||||||
bool dynamic_state3_enables{}; ///< Has all enables features of dynamic_state3.
|
bool dynamic_state3_enables{}; ///< Has all enables features of dynamic_state3.
|
||||||
|
|
Reference in New Issue