// Copyright (C) 2026 SharpEmu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later using SharpEmu.Libs.VideoOut; using SharpEmu.ShaderCompiler; using SharpEmu.ShaderCompiler.Vulkan; namespace SharpEmu.Libs.Gpu.Vulkan; /// /// Vulkan backend for the guest-GPU seam: SPIR-V codegen via /// SharpEmu.ShaderCompiler.Vulkan, rendering via a thin adapter over the existing /// VulkanVideoPresenter statics (folding the presenter into an instance type is /// follow-up work, not a seam concern). /// internal sealed class VulkanGuestGpuBackend : IGuestGpuBackend { public string BackendName => "Vulkan"; private static readonly IGuestCompiledShader DepthOnlyFragmentShader = new VulkanCompiledGuestShader(SpirvFixedShaders.CreateDepthOnlyFragment()); public bool TryCompileVertexShader( Gen5ShaderState state, Gen5ShaderEvaluation evaluation, out IGuestCompiledShader? shader, out string error, int globalBufferBase = 0, int totalGlobalBufferCount = -1, int imageBindingBase = 0, int scalarRegisterBufferIndex = -1, int requiredVertexOutputCount = 0, ulong storageBufferOffsetAlignment = 1) { shader = null; if (!Gen5SpirvTranslator.TryCompileVertexShader( state, evaluation, out var compiled, out error, globalBufferBase, totalGlobalBufferCount, imageBindingBase, scalarRegisterBufferIndex, requiredVertexOutputCount, storageBufferOffsetAlignment)) { return false; } shader = new VulkanCompiledGuestShader(compiled.Spirv); return true; } public bool TryCompilePixelShader( Gen5ShaderState state, Gen5ShaderEvaluation evaluation, IReadOnlyList outputs, out IGuestCompiledShader? shader, out string error, int globalBufferBase = 0, int totalGlobalBufferCount = -1, int imageBindingBase = 0, int scalarRegisterBufferIndex = -1, uint pixelInputEnable = 0, uint pixelInputAddress = 0, IReadOnlyList? pixelInputCntl = null, ulong storageBufferOffsetAlignment = 1) { shader = null; if (!Gen5SpirvTranslator.TryCompilePixelShader( state, evaluation, outputs, out var compiled, out error, globalBufferBase, totalGlobalBufferCount, imageBindingBase, scalarRegisterBufferIndex, pixelInputEnable, pixelInputAddress, pixelInputCntl, storageBufferOffsetAlignment)) { return false; } shader = new VulkanCompiledGuestShader(compiled.Spirv); return true; } public bool TryCompileComputeShader( Gen5ShaderState state, Gen5ShaderEvaluation evaluation, uint localSizeX, uint localSizeY, uint localSizeZ, out IGuestCompiledShader? shader, out string error, int totalGlobalBufferCount = -1, int initialScalarBufferIndex = -1, uint waveLaneCount = 32, ulong storageBufferOffsetAlignment = 1) { shader = null; if (!Gen5SpirvTranslator.TryCompileComputeShader( state, evaluation, localSizeX, localSizeY, localSizeZ, out var compiled, out error, totalGlobalBufferCount, initialScalarBufferIndex, waveLaneCount, storageBufferOffsetAlignment)) { return false; } shader = new VulkanCompiledGuestShader(compiled.Spirv); return true; } public IGuestCompiledShader GetDepthOnlyFragmentShader() => DepthOnlyFragmentShader; public void EnsureStarted(uint width, uint height) => VulkanVideoPresenter.EnsureStarted(width, height); public void HideSplashScreen() => VulkanVideoPresenter.HideSplashScreen(); public void Submit(byte[] bgraFrame, uint width, uint height) => VulkanVideoPresenter.Submit(bgraFrame, width, height); public void SubmitGuestDraw(GuestDrawKind drawKind, uint width, uint height) => VulkanVideoPresenter.SubmitGuestDraw(drawKind, width, height); public void SubmitTranslatedDraw( IGuestCompiledShader pixelShader, IReadOnlyList textures, IReadOnlyList globalMemoryBuffers, uint width, uint height, uint attributeCount, IGuestCompiledShader? vertexShader = null, uint vertexCount = 3, uint instanceCount = 1, uint primitiveType = 4, GuestIndexBuffer? indexBuffer = null, IReadOnlyList? vertexBuffers = null, GuestRenderState? renderState = null) => VulkanVideoPresenter.SubmitTranslatedDraw( Spirv(pixelShader), textures, globalMemoryBuffers, width, height, attributeCount, vertexShader is null ? null : Spirv(vertexShader), vertexCount, instanceCount, primitiveType, indexBuffer, vertexBuffers, renderState); public void SubmitDepthOnlyTranslatedDraw( IGuestCompiledShader pixelShader, IReadOnlyList textures, IReadOnlyList globalMemoryBuffers, uint attributeCount, GuestDepthTarget depthTarget, IGuestCompiledShader? vertexShader = null, uint vertexCount = 3, uint instanceCount = 1, uint primitiveType = 4, GuestIndexBuffer? indexBuffer = null, IReadOnlyList? vertexBuffers = null, GuestRenderState? renderState = null, ulong shaderAddress = 0, int baseVertex = 0) => VulkanVideoPresenter.SubmitDepthOnlyTranslatedDraw( Spirv(pixelShader), textures, globalMemoryBuffers, attributeCount, depthTarget, vertexShader is null ? null : Spirv(vertexShader), vertexCount, instanceCount, primitiveType, indexBuffer, vertexBuffers, renderState, shaderAddress, baseVertex); public void SubmitOffscreenTranslatedDraw( IGuestCompiledShader pixelShader, IReadOnlyList textures, IReadOnlyList globalMemoryBuffers, uint attributeCount, IReadOnlyList targets, IGuestCompiledShader? vertexShader = null, uint vertexCount = 3, uint instanceCount = 1, uint primitiveType = 4, GuestIndexBuffer? indexBuffer = null, IReadOnlyList? vertexBuffers = null, GuestRenderState? renderState = null, GuestDepthTarget? depthTarget = null, ulong shaderAddress = 0, int baseVertex = 0) => VulkanVideoPresenter.SubmitOffscreenTranslatedDraw( Spirv(pixelShader), textures, globalMemoryBuffers, attributeCount, targets, vertexShader is null ? null : Spirv(vertexShader), vertexCount, instanceCount, primitiveType, indexBuffer, vertexBuffers, renderState, depthTarget, shaderAddress, baseVertex); public void SubmitStorageTranslatedDraw( IGuestCompiledShader pixelShader, IReadOnlyList textures, IReadOnlyList globalMemoryBuffers, uint attributeCount, uint width, uint height, ulong shaderAddress = 0) => VulkanVideoPresenter.SubmitStorageTranslatedDraw( Spirv(pixelShader), textures, globalMemoryBuffers, attributeCount, width, height, shaderAddress); public long SubmitComputeDispatch( ulong shaderAddress, IGuestCompiledShader computeShader, IReadOnlyList textures, IReadOnlyList globalMemoryBuffers, uint groupCountX, uint groupCountY, uint groupCountZ, uint baseGroupX, uint baseGroupY, uint baseGroupZ, uint localSizeX, uint localSizeY, uint localSizeZ, bool isIndirect, bool writesGlobalMemory, uint threadCountX = uint.MaxValue, uint threadCountY = uint.MaxValue, uint threadCountZ = uint.MaxValue) => VulkanVideoPresenter.SubmitComputeDispatch( shaderAddress, Spirv(computeShader), textures, globalMemoryBuffers, groupCountX, groupCountY, groupCountZ, baseGroupX, baseGroupY, baseGroupZ, localSizeX, localSizeY, localSizeZ, isIndirect, writesGlobalMemory, threadCountX, threadCountY, threadCountZ); public bool TrySubmitGuestImage( ulong address, uint width, uint height, uint pitchInPixel) => VulkanVideoPresenter.TrySubmitGuestImage(address, width, height, pitchInPixel); public bool TrySubmitOrderedGuestImageFlip( int videoOutHandle, int displayBufferIndex, ulong address, uint width, uint height, uint pitchInPixel) => VulkanVideoPresenter.TrySubmitOrderedGuestImageFlip( videoOutHandle, displayBufferIndex, address, width, height, pitchInPixel); public void RegisterKnownDisplayBuffer(ulong address, uint guestFormat) => VulkanVideoPresenter.RegisterKnownDisplayBuffer(address, guestFormat); public bool IsGpuGuestImageAvailable(ulong address, uint format, uint numberType) => VulkanVideoPresenter.IsGpuGuestImageAvailable(address, format, numberType); public bool TrySubmitGuestImageBlit( ulong sourceAddress, uint sourceWidth, uint sourceHeight, uint sourceFormat, uint sourceNumberType, ulong destinationAddress, uint destinationWidth, uint destinationHeight, uint destinationFormat, uint destinationNumberType) => VulkanVideoPresenter.TrySubmitGuestImageBlit( sourceAddress, sourceWidth, sourceHeight, sourceFormat, sourceNumberType, destinationAddress, destinationWidth, destinationHeight, destinationFormat, destinationNumberType); public bool TryGetRenderTargetOutputKind(uint dataFormat, uint numberType, out Gen5PixelOutputKind outputKind) { if (VulkanVideoPresenter.TryDecodeRenderTargetFormat(dataFormat, numberType, out var format)) { outputKind = format.OutputKind; return true; } outputKind = default; return false; } public IDisposable EnterGuestQueue(string queueName, ulong submissionId) => VulkanVideoPresenter.EnterGuestQueue(queueName, submissionId); public long SubmitOrderedGuestAction(Action action, string debugName) => VulkanVideoPresenter.SubmitOrderedGuestAction(action, debugName); public long SubmitOrderedGuestFlipWait(int videoOutHandle, int displayBufferIndex) => VulkanVideoPresenter.SubmitOrderedGuestFlipWait(videoOutHandle, displayBufferIndex); public bool WaitForGuestWork(long workSequence, int timeoutMilliseconds = Timeout.Infinite) => VulkanVideoPresenter.WaitForGuestWork(workSequence, timeoutMilliseconds); public long CurrentGuestWorkSequenceForDiagnostics => VulkanVideoPresenter.CurrentGuestWorkSequenceForDiagnostics; public bool IsGuestImageUploadKnown(ulong address, uint format, uint numberType) => VulkanVideoPresenter.IsGuestImageUploadKnown(address, format, numberType); public bool GuestImageWantsInitialData(ulong address) => VulkanVideoPresenter.GuestImageWantsInitialData(address); public void ProvideGuestImageInitialData(ulong address, byte[] rgbaPixels) => VulkanVideoPresenter.ProvideGuestImageInitialData(address, rgbaPixels); public void SubmitGuestImageFill(ulong address, uint fillValue) => VulkanVideoPresenter.SubmitGuestImageFill(address, fillValue); public void SubmitGuestImageWrite(ulong address, byte[] pixels, uint rowOffset = 0) => VulkanVideoPresenter.SubmitGuestImageWrite(address, pixels, rowOffset); public bool SupportsPartialImageWrite => true; public void RequestCpuWrittenGuestImageSync(ulong scopeAddress = 0, ulong scopeByteCount = ulong.MaxValue) => VulkanVideoPresenter.RequestCpuWrittenGuestImageSync(scopeAddress, scopeByteCount); public bool TryGetGuestImageExtent(ulong address, out uint width, out uint height, out ulong byteCount) => VulkanVideoPresenter.TryGetGuestImageExtent(address, out width, out height, out byteCount); public IReadOnlyList<(ulong Address, uint Width, uint Height, ulong ByteCount)> GetGuestImageExtents() => VulkanVideoPresenter.GetGuestImageExtents(); public bool IsTextureContentCached(in TextureContentIdentity identity) => VulkanVideoPresenter.IsTextureContentCached(identity); public void AttachGuestMemory(SharpEmu.HLE.ICpuMemory memory) => VulkanVideoPresenter.AttachGuestMemory(memory); public ulong GuestStorageBufferOffsetAlignment => VulkanVideoPresenter.GuestStorageBufferOffsetAlignment; public void CountShaderCompilation() => VulkanVideoPresenter.CountSpirvCompilation(); public (long Draws, double DrawMs, long Pipelines, long ShaderCompilations) ReadAndResetPerfCounters() => VulkanVideoPresenter.ReadAndResetPerfCounters(); public void RequestClose() => VulkanVideoPresenter.RequestClose(); private static byte[] Spirv(IGuestCompiledShader shader) => shader is VulkanCompiledGuestShader vulkanShader ? vulkanShader.Spirv : throw new InvalidOperationException( $"shader handle of type {shader.GetType().Name} was not compiled by the Vulkan backend"); }