sharpemu/src/SharpEmu.ShaderCompiler.Vulkan/BlitShaders.cs

174 lines
12 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
namespace SharpEmu.ShaderCompiler.Vulkan;
// Assembles the fullscreen-triangle vertex shader, the color-to-multisample-depth fragment
// shader, and the multisample depth readback the tests use to check it.
public static class BlitShaders
{
public static byte[] CreateFullscreenTriangleVertex()
{
var module = new SpirvModuleBuilder();
module.AddCapability(SpirvCapability.Shader);
var voidType = module.TypeVoid();
var intType = module.TypeInt(32, signed: true);
var uintType = module.TypeInt(32, signed: false);
var floatType = module.TypeFloat(32);
var vec2Type = module.TypeVector(floatType, 2);
var vec4Type = module.TypeVector(floatType, 4);
var vertexIndexVar = module.AddGlobalVariable(module.TypePointer(SpirvStorageClass.Input, intType), SpirvStorageClass.Input);
module.AddName(vertexIndexVar, "gl_VertexIndex");
module.AddDecoration(vertexIndexVar, SpirvDecoration.BuiltIn, (uint)SpirvBuiltIn.VertexIndex);
var positionVar = module.AddGlobalVariable(module.TypePointer(SpirvStorageClass.Output, vec4Type), SpirvStorageClass.Output);
module.AddName(positionVar, "gl_Position");
module.AddDecoration(positionVar, SpirvDecoration.BuiltIn, (uint)SpirvBuiltIn.Position);
var uvVar = module.AddGlobalVariable(module.TypePointer(SpirvStorageClass.Output, vec2Type), SpirvStorageClass.Output);
module.AddName(uvVar, "uv");
module.AddDecoration(uvVar, SpirvDecoration.Location, 0);
var main = module.BeginFunction(voidType, module.TypeFunction(voidType));
module.AddName(main, "main");
module.AddLabel();
var vertexIndex = module.AddInstruction(SpirvOp.Bitcast, uintType, module.AddInstruction(SpirvOp.Load, intType, vertexIndexVar));
var xBits = module.AddInstruction(SpirvOp.ShiftLeftLogical, uintType, module.AddInstruction(SpirvOp.BitwiseAnd, uintType, vertexIndex, module.Constant(uintType, 1)), module.Constant(uintType, 2));
var yBits = module.AddInstruction(SpirvOp.ShiftLeftLogical, uintType, module.AddInstruction(SpirvOp.BitwiseAnd, uintType, vertexIndex, module.Constant(uintType, 2)), module.Constant(uintType, 1));
var x = module.AddInstruction(SpirvOp.ConvertUToF, floatType, xBits);
var y = module.AddInstruction(SpirvOp.ConvertUToF, floatType, yBits);
var position = module.AddInstruction(SpirvOp.CompositeConstruct, vec2Type, x, y);
var one = module.ConstantFloat(floatType, 1.0f);
var ones = module.ConstantComposite(vec2Type, one, one);
var centered = module.AddInstruction(SpirvOp.FSub, vec2Type, position, ones);
var centeredX = module.AddInstruction(SpirvOp.CompositeExtract, floatType, centered, 0);
var centeredY = module.AddInstruction(SpirvOp.CompositeExtract, floatType, centered, 1);
var clip = module.AddInstruction(SpirvOp.CompositeConstruct, vec4Type, centeredX, centeredY, module.ConstantFloat(floatType, 0.0f), one);
module.AddStatement(SpirvOp.Store, positionVar, clip);
var half = module.ConstantFloat(floatType, 0.5f);
var halves = module.ConstantComposite(vec2Type, half, half);
module.AddStatement(SpirvOp.Store, uvVar, module.AddInstruction(SpirvOp.FMul, vec2Type, position, halves));
module.AddStatement(SpirvOp.Return);
module.EndFunction();
module.AddEntryPoint(SpirvExecutionModel.Vertex, main, "main", [vertexIndexVar, positionVar, uvVar]);
return module.Build();
}
// Each sample of the depth output takes one component of the source color texel.
public static byte[] CreateColorToMultisampleDepthFragment()
{
var module = new SpirvModuleBuilder();
module.AddCapability(SpirvCapability.Shader);
module.AddCapability(SpirvCapability.SampleRateShading);
module.AddCapability(SpirvCapability.ImageQuery);
var voidType = module.TypeVoid();
var intType = module.TypeInt(32, signed: true);
var floatType = module.TypeFloat(32);
var vec2Type = module.TypeVector(floatType, 2);
var vec4Type = module.TypeVector(floatType, 4);
var ivec2Type = module.TypeVector(intType, 2);
var imageType = module.TypeImage(floatType, SpirvImageDim.Dim2D, depth: false, arrayed: false, multisampled: false, sampled: 1, SpirvImageFormat.Unknown);
var colorVar = module.AddGlobalVariable(module.TypePointer(SpirvStorageClass.UniformConstant, imageType), SpirvStorageClass.UniformConstant);
module.AddName(colorVar, "color");
module.AddDecoration(colorVar, SpirvDecoration.DescriptorSet, 0);
module.AddDecoration(colorVar, SpirvDecoration.Binding, 0);
var uvVar = module.AddGlobalVariable(module.TypePointer(SpirvStorageClass.Input, vec2Type), SpirvStorageClass.Input);
module.AddName(uvVar, "uv");
module.AddDecoration(uvVar, SpirvDecoration.Location, 0);
var sampleIdVar = module.AddGlobalVariable(module.TypePointer(SpirvStorageClass.Input, intType), SpirvStorageClass.Input);
module.AddName(sampleIdVar, "gl_SampleID");
module.AddDecoration(sampleIdVar, SpirvDecoration.BuiltIn, (uint)SpirvBuiltIn.SampleId);
module.AddDecoration(sampleIdVar, SpirvDecoration.Flat);
var fragDepthVar = module.AddGlobalVariable(module.TypePointer(SpirvStorageClass.Output, floatType), SpirvStorageClass.Output);
module.AddName(fragDepthVar, "gl_FragDepth");
module.AddDecoration(fragDepthVar, SpirvDecoration.BuiltIn, (uint)SpirvBuiltIn.FragDepth);
var main = module.BeginFunction(voidType, module.TypeFunction(voidType));
module.AddName(main, "main");
module.AddLabel();
var uv = module.AddInstruction(SpirvOp.Load, vec2Type, uvVar);
var image = module.AddInstruction(SpirvOp.Load, imageType, colorVar);
var size = module.AddInstruction(SpirvOp.ImageQuerySizeLod, ivec2Type, image, module.Constant(intType, 0));
var sizeF = module.AddInstruction(SpirvOp.ConvertSToF, vec2Type, size);
var scaled = module.AddInstruction(SpirvOp.FMul, vec2Type, uv, sizeF);
var coord = module.AddInstruction(SpirvOp.ConvertFToS, ivec2Type, scaled);
var texel = module.AddInstruction(SpirvOp.ImageFetch, vec4Type, image, coord, 0x2, module.Constant(intType, 0));
var sampleId = module.AddInstruction(SpirvOp.Load, intType, sampleIdVar);
var depth = module.AddInstruction(SpirvOp.VectorExtractDynamic, floatType, texel, sampleId);
module.AddStatement(SpirvOp.Store, fragDepthVar, depth);
module.AddStatement(SpirvOp.Return);
module.EndFunction();
module.AddExecutionMode(main, SpirvExecutionMode.OriginUpperLeft);
module.AddExecutionMode(main, SpirvExecutionMode.DepthReplacing);
module.AddEntryPoint(SpirvExecutionModel.Fragment, main, "main", [colorVar, uvVar, sampleIdVar, fragDepthVar]);
return module.Build();
}
// Test helper: writes the depth of each sample of texel (0, 0) as raw float bits.
public static byte[] CreateMultisampleDepthSampleReadback()
{
var module = new SpirvModuleBuilder();
module.AddCapability(SpirvCapability.Shader);
var voidType = module.TypeVoid();
var boolType = module.TypeBool();
var intType = module.TypeInt(32, signed: true);
var uintType = module.TypeInt(32, signed: false);
var floatType = module.TypeFloat(32);
var vec4Type = module.TypeVector(floatType, 4);
var uvec3Type = module.TypeVector(uintType, 3);
var ivec2Type = module.TypeVector(intType, 2);
var imageType = module.TypeImage(floatType, SpirvImageDim.Dim2D, depth: false, arrayed: false, multisampled: true, sampled: 1, SpirvImageFormat.Unknown);
var depthVar = module.AddGlobalVariable(module.TypePointer(SpirvStorageClass.UniformConstant, imageType), SpirvStorageClass.UniformConstant);
module.AddName(depthVar, "input_depth");
module.AddDecoration(depthVar, SpirvDecoration.DescriptorSet, 0);
module.AddDecoration(depthVar, SpirvDecoration.Binding, 0);
var runtimeArray = module.TypeRuntimeArray(uintType);
module.AddDecoration(runtimeArray, SpirvDecoration.ArrayStride, 4);
var bufferStruct = module.TypeStruct(runtimeArray);
module.AddDecoration(bufferStruct, SpirvDecoration.Block);
module.AddMemberDecoration(bufferStruct, 0, SpirvDecoration.Offset, 0);
var outputVar = module.AddGlobalVariable(module.TypePointer(SpirvStorageClass.StorageBuffer, bufferStruct), SpirvStorageClass.StorageBuffer);
module.AddName(outputVar, "output_data");
module.AddDecoration(outputVar, SpirvDecoration.DescriptorSet, 0);
module.AddDecoration(outputVar, SpirvDecoration.Binding, 1);
var pushStruct = module.TypeStruct(uintType);
module.AddDecoration(pushStruct, SpirvDecoration.Block);
module.AddMemberDecoration(pushStruct, 0, SpirvDecoration.Offset, 0);
var pushVar = module.AddGlobalVariable(module.TypePointer(SpirvStorageClass.PushConstant, pushStruct), SpirvStorageClass.PushConstant);
module.AddName(pushVar, "push");
var invocationVar = module.AddGlobalVariable(module.TypePointer(SpirvStorageClass.Input, uvec3Type), SpirvStorageClass.Input);
module.AddDecoration(invocationVar, SpirvDecoration.BuiltIn, (uint)SpirvBuiltIn.LocalInvocationId);
var main = module.BeginFunction(voidType, module.TypeFunction(voidType));
module.AddName(main, "main");
module.AddLabel();
var invocation = module.AddInstruction(SpirvOp.Load, uvec3Type, invocationVar);
var sampleId = module.AddInstruction(SpirvOp.CompositeExtract, uintType, invocation, 0);
var samplesPointer = module.AddInstruction(SpirvOp.AccessChain, module.TypePointer(SpirvStorageClass.PushConstant, uintType), pushVar, module.Constant(uintType, 0));
var samples = module.AddInstruction(SpirvOp.Load, uintType, samplesPointer);
var inRange = module.AddInstruction(SpirvOp.ULessThan, boolType, sampleId, samples);
var bodyLabel = module.AllocateId();
var mergeLabel = module.AllocateId();
module.AddStatement(SpirvOp.SelectionMerge, mergeLabel, 0);
module.AddStatement(SpirvOp.BranchConditional, inRange, bodyLabel, mergeLabel);
module.AddLabel(bodyLabel);
var image = module.AddInstruction(SpirvOp.Load, imageType, depthVar);
var zero = module.Constant(intType, 0);
var origin = module.ConstantComposite(ivec2Type, zero, zero);
var sampleIndex = module.AddInstruction(SpirvOp.Bitcast, intType, sampleId);
var texel = module.AddInstruction(SpirvOp.ImageFetch, vec4Type, image, origin, 0x40, sampleIndex);
var depth = module.AddInstruction(SpirvOp.CompositeExtract, floatType, texel, 0);
var bits = module.AddInstruction(SpirvOp.Bitcast, uintType, depth);
var target = module.AddInstruction(SpirvOp.AccessChain, module.TypePointer(SpirvStorageClass.StorageBuffer, uintType), outputVar, module.Constant(uintType, 0), sampleId);
module.AddStatement(SpirvOp.Store, target, bits);
module.AddStatement(SpirvOp.Branch, mergeLabel);
module.AddLabel(mergeLabel);
module.AddStatement(SpirvOp.Return);
module.EndFunction();
module.AddExecutionMode(main, SpirvExecutionMode.LocalSize, 4, 1, 1);
module.AddEntryPoint(SpirvExecutionModel.GLCompute, main, "main", [invocationVar, depthVar, outputVar, pushVar]);
return module.Build();
}
}