mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-21 02:16:21 +08:00
[video] Add Vulkan presentation
This commit is contained in:
@@ -2,9 +2,10 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
using SharpEmu.HLE;
|
using SharpEmu.HLE;
|
||||||
|
using SharpEmu.Libs.Kernel;
|
||||||
using SharpEmu.Libs.VideoOut;
|
using SharpEmu.Libs.VideoOut;
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Threading;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace SharpEmu.Libs.Agc;
|
namespace SharpEmu.Libs.Agc;
|
||||||
|
|
||||||
@@ -16,7 +17,9 @@ public static class AgcExports
|
|||||||
private const uint ItIndexBufferSize = 0x13;
|
private const uint ItIndexBufferSize = 0x13;
|
||||||
private const uint ItIndexBase = 0x26;
|
private const uint ItIndexBase = 0x26;
|
||||||
private const uint ItIndexType = 0x2A;
|
private const uint ItIndexType = 0x2A;
|
||||||
|
private const uint ItNumInstances = 0x2F;
|
||||||
private const uint ItDrawIndexOffset2 = 0x35;
|
private const uint ItDrawIndexOffset2 = 0x35;
|
||||||
|
private const uint ItEventWrite = 0x46;
|
||||||
private const uint ItSetShReg = 0x76;
|
private const uint ItSetShReg = 0x76;
|
||||||
private const uint RZero = 0x00;
|
private const uint RZero = 0x00;
|
||||||
private const uint RDrawIndexAuto = 0x04;
|
private const uint RDrawIndexAuto = 0x04;
|
||||||
@@ -25,11 +28,16 @@ public static class AgcExports
|
|||||||
private const uint RShRegsIndirect = 0x11;
|
private const uint RShRegsIndirect = 0x11;
|
||||||
private const uint RCxRegsIndirect = 0x12;
|
private const uint RCxRegsIndirect = 0x12;
|
||||||
private const uint RUcRegsIndirect = 0x13;
|
private const uint RUcRegsIndirect = 0x13;
|
||||||
|
private const uint RAcquireMem = 0x14;
|
||||||
private const uint RFlip = 0x17;
|
private const uint RFlip = 0x17;
|
||||||
private const uint SpiShaderPgmLoPs = 0x8;
|
private const uint SpiShaderPgmLoPs = 0x8;
|
||||||
private const uint SpiShaderPgmHiPs = 0x9;
|
private const uint SpiShaderPgmHiPs = 0x9;
|
||||||
private const uint SpiShaderPgmLoEs = 0xC8;
|
private const uint SpiShaderPgmLoEs = 0xC8;
|
||||||
private const uint SpiShaderPgmHiEs = 0xC9;
|
private const uint SpiShaderPgmHiEs = 0xC9;
|
||||||
|
private const uint SpiPsInputEna = 0x1B3;
|
||||||
|
private const uint SpiPsInputAddr = 0x1B4;
|
||||||
|
private const uint ComputePgmLo = 0x20C;
|
||||||
|
private const uint ComputePgmHi = 0x20D;
|
||||||
private const uint SpiPsInputCntl0 = 0x191;
|
private const uint SpiPsInputCntl0 = 0x191;
|
||||||
private const uint VgtPrimitiveType = 0x242;
|
private const uint VgtPrimitiveType = 0x242;
|
||||||
private const uint PsTextureUserDataRegister = 0xC;
|
private const uint PsTextureUserDataRegister = 0xC;
|
||||||
@@ -37,6 +45,10 @@ public static class AgcExports
|
|||||||
private const uint Gen5TextureType2D = 9;
|
private const uint Gen5TextureType2D = 9;
|
||||||
private const ulong VideoOutPixelFormatA8R8G8B8Srgb = 0x80000000;
|
private const ulong VideoOutPixelFormatA8R8G8B8Srgb = 0x80000000;
|
||||||
private const ulong VideoOutPixelFormatA8B8G8R8Srgb = 0x80002200;
|
private const ulong VideoOutPixelFormatA8B8G8R8Srgb = 0x80002200;
|
||||||
|
private const uint RegisterDefaultsVersion7 = 7;
|
||||||
|
private const uint RegisterDefaultsVersion8 = 8;
|
||||||
|
private const int RegisterDefaultsSize = 0x40;
|
||||||
|
private const int RegisterDefaultBlockSize = 16 * 8;
|
||||||
|
|
||||||
private const ulong ShaderUserDataOffset = 0x08;
|
private const ulong ShaderUserDataOffset = 0x08;
|
||||||
private const ulong ShaderCodeOffset = 0x10;
|
private const ulong ShaderCodeOffset = 0x10;
|
||||||
@@ -58,9 +70,86 @@ public static class AgcExports
|
|||||||
private const ulong ShaderSpecialVgtGsOutPrimTypeOffset = 0x20;
|
private const ulong ShaderSpecialVgtGsOutPrimTypeOffset = 0x20;
|
||||||
private const ulong ShaderSpecialGeUserVgprEnOffset = 0x28;
|
private const ulong ShaderSpecialGeUserVgprEnOffset = 0x28;
|
||||||
private const uint CbSetShRegisterRangeMarker = 0x6875000D;
|
private const uint CbSetShRegisterRangeMarker = 0x6875000D;
|
||||||
|
private static readonly object _submitTraceGate = new();
|
||||||
|
private static readonly HashSet<uint> _tracedDcbSizes = new();
|
||||||
|
private static readonly HashSet<(ulong Es, ulong Ps, GuestDrawKind Kind)> _tracedShaderTranslations = new();
|
||||||
private static readonly object _softwarePresenterGate = new();
|
private static readonly object _softwarePresenterGate = new();
|
||||||
private static readonly Dictionary<(ulong Source, ulong Destination), ulong> _softwarePresenterFingerprints = new();
|
private static readonly Dictionary<(ulong Source, ulong Destination), ulong> _softwarePresenterFingerprints = new();
|
||||||
private static int _submitTraceCaptured;
|
private static readonly object _registerDefaultsGate = new();
|
||||||
|
private static readonly ConditionalWeakTable<object, RegisterDefaultsAllocation> _registerDefaultsAllocations = new();
|
||||||
|
|
||||||
|
private static readonly RegisterDefaultGroup[] PrimaryRegisterDefaults =
|
||||||
|
[
|
||||||
|
new(0, 3, 0x0BC65DA4, [new(0x08F, 0)]),
|
||||||
|
new(0, 4, 0x9E5AD592, [new(0x08E, 0)]),
|
||||||
|
new(0, 12, 0x6DE4C312, [new(0x203, 0)]),
|
||||||
|
new(0, 72, 0x38E92C91,
|
||||||
|
[
|
||||||
|
new(0x318, 0),
|
||||||
|
new(0x31B, 0),
|
||||||
|
new(0x31C, 0),
|
||||||
|
new(0x31D, 0),
|
||||||
|
new(0x31E, 0x48),
|
||||||
|
new(0x31F, 0),
|
||||||
|
new(0x321, 0),
|
||||||
|
new(0x323, 0),
|
||||||
|
new(0x324, 0),
|
||||||
|
new(0x325, 0),
|
||||||
|
new(0x390, 0),
|
||||||
|
new(0x398, 0),
|
||||||
|
new(0x3A0, 0),
|
||||||
|
new(0x3A8, 0),
|
||||||
|
new(0x3B0, 0),
|
||||||
|
new(0x3B8, 0x0006C000),
|
||||||
|
]),
|
||||||
|
new(0, 73, 0x0B177B43, [new(0x00C, 0), new(0x00D, 0x40004000)]),
|
||||||
|
new(0, 74, 0x48531062, [new(0x191, 0)]),
|
||||||
|
new(0, 76, 0x7690AF6F,
|
||||||
|
[
|
||||||
|
new(0x10F, 0x4E7E0000),
|
||||||
|
new(0x111, 0x4E7E0000),
|
||||||
|
new(0x113, 0x4E7E0000),
|
||||||
|
new(0x110, 0),
|
||||||
|
new(0x112, 0),
|
||||||
|
new(0x114, 0),
|
||||||
|
new(0x094, 0x80000000),
|
||||||
|
new(0x095, 0x40004000),
|
||||||
|
new(0x0B4, 0),
|
||||||
|
new(0x0B5, 0),
|
||||||
|
]),
|
||||||
|
new(1, 13, 0xC918DF3E, [new(0x20C, 0), new(0x20D, 0)]),
|
||||||
|
new(1, 14, 0xC9751C9C, [new(0x0C8, 0), new(0x0C9, 0)]),
|
||||||
|
new(1, 18, 0xC9E01B31, [new(0x008, 0), new(0x009, 0)]),
|
||||||
|
new(2, 3, 0x105971C2, [new(0x25B, 0)]),
|
||||||
|
new(2, 7, 0x40D49AD1, [new(0x262, 0)]),
|
||||||
|
new(2, 12, 0x9EBFAB10, [new(0x242, 0)]),
|
||||||
|
];
|
||||||
|
|
||||||
|
private static readonly RegisterDefaultGroup[] InternalRegisterDefaults =
|
||||||
|
[
|
||||||
|
new(0, 0, 0x8FB4EDB5, [new(0x00E, 0)]),
|
||||||
|
new(0, 1, 0xB994AD29, [new(0x2AF, 0)]),
|
||||||
|
new(0, 2, 0xD427322F, [new(0x314, 0)]),
|
||||||
|
new(0, 3, 0xF58FEA31, [new(0x1B5, 0)]),
|
||||||
|
new(1, 0, 0x6AC156EF, [new(0x216, 0)]),
|
||||||
|
new(1, 1, 0x6AC15610, [new(0x217, 0)]),
|
||||||
|
new(1, 2, 0x6AC15009, [new(0x219, 0)]),
|
||||||
|
new(1, 3, 0x6AC153BA, [new(0x21A, 0)]),
|
||||||
|
new(1, 4, 0xBE7DCD73, [new(0x27D, 0)]),
|
||||||
|
new(1, 5, 0x0C4B1438, [new(0x22A, 0)]),
|
||||||
|
new(1, 6, 0xDB00D71A, [new(0x204, 0)]),
|
||||||
|
new(1, 7, 0xDB00D249, [new(0x205, 0)]),
|
||||||
|
new(1, 8, 0xDB00EC60, [new(0x206, 0)]),
|
||||||
|
new(1, 9, 0x0C4D6FE4, [new(0x080, 0)]),
|
||||||
|
new(1, 10, 0x0C4A80EF, [new(0x100, 0)]),
|
||||||
|
new(1, 11, 0x0DD283E7, [new(0x006, 0)]),
|
||||||
|
new(1, 12, 0xC620E68C, [new(0x081, 0)]),
|
||||||
|
new(1, 13, 0xC67EFACF, [new(0x101, 0)]),
|
||||||
|
new(1, 14, 0xD9E6D9F7, [new(0x001, 0)]),
|
||||||
|
new(2, 0, 0x31F34B9F, [new(0x24F, 0)]),
|
||||||
|
new(2, 1, 0xAC0F9E76, [new(0x80003FFF, 0)]),
|
||||||
|
new(2, 2, 0x929FD95D, [new(0x250, 0)]),
|
||||||
|
];
|
||||||
|
|
||||||
private readonly record struct TextureDescriptor(
|
private readonly record struct TextureDescriptor(
|
||||||
ulong Address,
|
ulong Address,
|
||||||
@@ -70,6 +159,57 @@ public static class AgcExports
|
|||||||
uint TileMode,
|
uint TileMode,
|
||||||
uint Type);
|
uint Type);
|
||||||
|
|
||||||
|
private sealed class SubmittedDcbState
|
||||||
|
{
|
||||||
|
public Dictionary<uint, uint> CxRegisters { get; } = new();
|
||||||
|
public Dictionary<uint, uint> ShRegisters { get; } = new();
|
||||||
|
public Dictionary<uint, uint> UcRegisters { get; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly record struct RegisterDefaultValue(uint Offset, uint Value);
|
||||||
|
|
||||||
|
private readonly record struct RegisterDefaultGroup(
|
||||||
|
uint Space,
|
||||||
|
uint Index,
|
||||||
|
uint Type,
|
||||||
|
RegisterDefaultValue[] Registers);
|
||||||
|
|
||||||
|
private sealed record RegisterDefaultsAllocation(ulong Primary, ulong Internal);
|
||||||
|
|
||||||
|
[SysAbiExport(
|
||||||
|
Nid = "23LRUSvYu1M",
|
||||||
|
ExportName = "sceAgcInit",
|
||||||
|
Target = Generation.Gen5,
|
||||||
|
LibraryName = "libSceAgc")]
|
||||||
|
public static int Init(CpuContext ctx)
|
||||||
|
{
|
||||||
|
var stateAddress = ctx[CpuRegister.Rdi];
|
||||||
|
var version = (uint)ctx[CpuRegister.Rsi];
|
||||||
|
if (stateAddress == 0 || !IsSupportedRegisterDefaultsVersion(version))
|
||||||
|
{
|
||||||
|
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
TraceAgc($"agc.init state=0x{stateAddress:X16} version={version}");
|
||||||
|
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
[SysAbiExport(
|
||||||
|
Nid = "2JtWUUiYBXs",
|
||||||
|
ExportName = "sceAgcGetRegisterDefaults2",
|
||||||
|
Target = Generation.Gen5,
|
||||||
|
LibraryName = "libSceAgc")]
|
||||||
|
public static int GetRegisterDefaults2(CpuContext ctx) =>
|
||||||
|
ReturnRegisterDefaults(ctx, internalDefaults: false);
|
||||||
|
|
||||||
|
[SysAbiExport(
|
||||||
|
Nid = "wRbq6ZjNop4",
|
||||||
|
ExportName = "sceAgcGetRegisterDefaults2Internal",
|
||||||
|
Target = Generation.Gen5,
|
||||||
|
LibraryName = "libSceAgc")]
|
||||||
|
public static int GetRegisterDefaults2Internal(CpuContext ctx) =>
|
||||||
|
ReturnRegisterDefaults(ctx, internalDefaults: true);
|
||||||
|
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
Nid = "f3dg2CSgRKY",
|
Nid = "f3dg2CSgRKY",
|
||||||
ExportName = "sceAgcCreateShader",
|
ExportName = "sceAgcCreateShader",
|
||||||
@@ -80,7 +220,7 @@ public static class AgcExports
|
|||||||
var destinationAddress = ctx[CpuRegister.Rdi];
|
var destinationAddress = ctx[CpuRegister.Rdi];
|
||||||
var headerAddress = ctx[CpuRegister.Rsi];
|
var headerAddress = ctx[CpuRegister.Rsi];
|
||||||
var codeAddress = ctx[CpuRegister.Rdx];
|
var codeAddress = ctx[CpuRegister.Rdx];
|
||||||
if (destinationAddress == 0 || headerAddress == 0 || codeAddress == 0)
|
if (headerAddress == 0 || codeAddress == 0)
|
||||||
{
|
{
|
||||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||||
}
|
}
|
||||||
@@ -128,7 +268,8 @@ public static class AgcExports
|
|||||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ctx.TryWriteUInt64(destinationAddress, headerAddress))
|
if (destinationAddress != 0 &&
|
||||||
|
!ctx.TryWriteUInt64(destinationAddress, headerAddress))
|
||||||
{
|
{
|
||||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||||
}
|
}
|
||||||
@@ -430,6 +571,136 @@ public static class AgcExports
|
|||||||
return ReturnPointer(ctx, commandAddress);
|
return ReturnPointer(ctx, commandAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SysAbiExport(
|
||||||
|
Nid = "tSBxhAPyytQ",
|
||||||
|
ExportName = "sceAgcDcbSetNumInstances",
|
||||||
|
Target = Generation.Gen5,
|
||||||
|
LibraryName = "libSceAgc")]
|
||||||
|
public static int DcbSetNumInstances(CpuContext ctx)
|
||||||
|
{
|
||||||
|
var commandBufferAddress = ctx[CpuRegister.Rdi];
|
||||||
|
var instanceCount = (uint)ctx[CpuRegister.Rsi];
|
||||||
|
if (commandBufferAddress == 0)
|
||||||
|
{
|
||||||
|
return ReturnPointer(ctx, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 2, out var commandAddress) ||
|
||||||
|
!TryWriteUInt32(ctx, commandAddress, Pm4(2, ItNumInstances, 0)) ||
|
||||||
|
!TryWriteUInt32(ctx, commandAddress + 4, instanceCount))
|
||||||
|
{
|
||||||
|
return ReturnPointer(ctx, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TraceAgc($"agc.dcb_set_num_instances buf=0x{commandBufferAddress:X16} cmd=0x{commandAddress:X16} count={instanceCount}");
|
||||||
|
return ReturnPointer(ctx, commandAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
[SysAbiExport(
|
||||||
|
Nid = "Yw0jKSqop+E",
|
||||||
|
ExportName = "sceAgcDcbDrawIndexAuto",
|
||||||
|
Target = Generation.Gen5,
|
||||||
|
LibraryName = "libSceAgc")]
|
||||||
|
public static int DcbDrawIndexAuto(CpuContext ctx)
|
||||||
|
{
|
||||||
|
var commandBufferAddress = ctx[CpuRegister.Rdi];
|
||||||
|
var indexCount = (uint)ctx[CpuRegister.Rsi];
|
||||||
|
var modifier = ctx[CpuRegister.Rdx];
|
||||||
|
if (commandBufferAddress == 0 || modifier != 0x4000_0000)
|
||||||
|
{
|
||||||
|
return ReturnPointer(ctx, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 7, out var commandAddress) ||
|
||||||
|
!TryWriteUInt32(ctx, commandAddress, Pm4(7, ItNop, RDrawIndexAuto)) ||
|
||||||
|
!TryWriteUInt32(ctx, commandAddress + 4, indexCount) ||
|
||||||
|
!TryWriteUInt32(ctx, commandAddress + 8, 0) ||
|
||||||
|
!TryWriteUInt32(ctx, commandAddress + 12, 0) ||
|
||||||
|
!TryWriteUInt32(ctx, commandAddress + 16, 0) ||
|
||||||
|
!TryWriteUInt32(ctx, commandAddress + 20, 0) ||
|
||||||
|
!TryWriteUInt32(ctx, commandAddress + 24, 0))
|
||||||
|
{
|
||||||
|
return ReturnPointer(ctx, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TraceAgc($"agc.dcb_draw_index_auto buf=0x{commandBufferAddress:X16} cmd=0x{commandAddress:X16} count={indexCount}");
|
||||||
|
return ReturnPointer(ctx, commandAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
[SysAbiExport(
|
||||||
|
Nid = "aJf+j5yntiU",
|
||||||
|
ExportName = "sceAgcDcbEventWrite",
|
||||||
|
Target = Generation.Gen5,
|
||||||
|
LibraryName = "libSceAgc")]
|
||||||
|
public static int DcbEventWrite(CpuContext ctx)
|
||||||
|
{
|
||||||
|
var commandBufferAddress = ctx[CpuRegister.Rdi];
|
||||||
|
var eventType = (uint)(ctx[CpuRegister.Rsi] & 0xFF);
|
||||||
|
var eventAddress = ctx[CpuRegister.Rdx];
|
||||||
|
if (commandBufferAddress == 0 || eventType > 0x3F || eventAddress != 0)
|
||||||
|
{
|
||||||
|
return ReturnPointer(ctx, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 2, out var commandAddress) ||
|
||||||
|
!TryWriteUInt32(ctx, commandAddress, Pm4(2, ItEventWrite, 0)) ||
|
||||||
|
!TryWriteUInt32(ctx, commandAddress + 4, eventType))
|
||||||
|
{
|
||||||
|
return ReturnPointer(ctx, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TraceAgc($"agc.dcb_event_write buf=0x{commandBufferAddress:X16} cmd=0x{commandAddress:X16} type={eventType}");
|
||||||
|
return ReturnPointer(ctx, commandAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
[SysAbiExport(
|
||||||
|
Nid = "57labkp+rSQ",
|
||||||
|
ExportName = "sceAgcDcbAcquireMem",
|
||||||
|
Target = Generation.Gen5,
|
||||||
|
LibraryName = "libSceAgc")]
|
||||||
|
public static int DcbAcquireMem(CpuContext ctx)
|
||||||
|
{
|
||||||
|
var commandBufferAddress = ctx[CpuRegister.Rdi];
|
||||||
|
var engine = (uint)(ctx[CpuRegister.Rsi] & 0xFF);
|
||||||
|
var cbDbOp = (uint)ctx[CpuRegister.Rdx];
|
||||||
|
var gcrControl = (uint)ctx[CpuRegister.Rcx];
|
||||||
|
var baseAddress = ctx[CpuRegister.R8];
|
||||||
|
var sizeBytes = ctx[CpuRegister.R9];
|
||||||
|
if (!TryReadUInt32(ctx, ctx[CpuRegister.Rsp] + sizeof(ulong), out var pollCycles))
|
||||||
|
{
|
||||||
|
return ReturnPointer(ctx, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
var noSize = sizeBytes == ulong.MaxValue;
|
||||||
|
if (commandBufferAddress == 0 ||
|
||||||
|
engine > 1 ||
|
||||||
|
(!noSize && (sizeBytes & 0xFF) != 0) ||
|
||||||
|
(!noSize && (sizeBytes >> 40) != 0) ||
|
||||||
|
(baseAddress & 0xFF) != 0 ||
|
||||||
|
(baseAddress >> 40) != 0)
|
||||||
|
{
|
||||||
|
return ReturnPointer(ctx, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 8, out var commandAddress) ||
|
||||||
|
!TryWriteUInt32(ctx, commandAddress, Pm4(8, ItNop, RAcquireMem)) ||
|
||||||
|
!TryWriteUInt32(ctx, commandAddress + 4, (engine << 31) | cbDbOp) ||
|
||||||
|
!TryWriteUInt32(ctx, commandAddress + 8, noSize ? 0 : (uint)(sizeBytes >> 8)) ||
|
||||||
|
!TryWriteUInt32(ctx, commandAddress + 12, 0) ||
|
||||||
|
!TryWriteUInt32(ctx, commandAddress + 16, (uint)(baseAddress >> 8)) ||
|
||||||
|
!TryWriteUInt32(ctx, commandAddress + 20, 0) ||
|
||||||
|
!TryWriteUInt32(ctx, commandAddress + 24, pollCycles / 40) ||
|
||||||
|
!TryWriteUInt32(ctx, commandAddress + 28, gcrControl))
|
||||||
|
{
|
||||||
|
return ReturnPointer(ctx, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TraceAgc(
|
||||||
|
$"agc.dcb_acquire_mem buf=0x{commandBufferAddress:X16} cmd=0x{commandAddress:X16} " +
|
||||||
|
$"engine={engine} cbdb=0x{cbDbOp:X8} gcr=0x{gcrControl:X8} base=0x{baseAddress:X16} size=0x{sizeBytes:X16}");
|
||||||
|
return ReturnPointer(ctx, commandAddress);
|
||||||
|
}
|
||||||
|
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
Nid = "l4fM9K-Lyks",
|
Nid = "l4fM9K-Lyks",
|
||||||
ExportName = "sceAgcDcbSetIndexBuffer",
|
ExportName = "sceAgcDcbSetIndexBuffer",
|
||||||
@@ -567,10 +838,20 @@ public static class AgcExports
|
|||||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
TraceAgc($"agc.driver_submit_dcb packet=0x{packetAddress:X16} addr=0x{commandAddress:X16} dwords={dwordCount}");
|
var tracePackets = false;
|
||||||
var tracePackets =
|
if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC"), "1", StringComparison.Ordinal))
|
||||||
string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC"), "1", StringComparison.Ordinal) &&
|
{
|
||||||
Interlocked.CompareExchange(ref _submitTraceCaptured, 1, 0) == 0;
|
lock (_submitTraceGate)
|
||||||
|
{
|
||||||
|
tracePackets = _tracedDcbSizes.Add(dwordCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tracePackets)
|
||||||
|
{
|
||||||
|
TraceAgc($"agc.driver_submit_dcb packet=0x{packetAddress:X16} addr=0x{commandAddress:X16} dwords={dwordCount}");
|
||||||
|
}
|
||||||
|
|
||||||
ParseSubmittedDcb(ctx, commandAddress, dwordCount, tracePackets);
|
ParseSubmittedDcb(ctx, commandAddress, dwordCount, tracePackets);
|
||||||
ctx[CpuRegister.Rax] = 0;
|
ctx[CpuRegister.Rax] = 0;
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
@@ -596,6 +877,8 @@ public static class AgcExports
|
|||||||
}
|
}
|
||||||
|
|
||||||
TextureDescriptor? presenterTexture = null;
|
TextureDescriptor? presenterTexture = null;
|
||||||
|
GuestDrawKind guestDrawKind = GuestDrawKind.None;
|
||||||
|
var state = new SubmittedDcbState();
|
||||||
var sawIndexedDraw = false;
|
var sawIndexedDraw = false;
|
||||||
var offset = 0u;
|
var offset = 0u;
|
||||||
while (offset < dwordCount)
|
while (offset < dwordCount)
|
||||||
@@ -625,12 +908,25 @@ public static class AgcExports
|
|||||||
presenterTexture = texture;
|
presenterTexture = texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ApplySubmittedRegisters(ctx, state, currentAddress, length, op, register);
|
||||||
|
|
||||||
if (op == ItDrawIndexOffset2 &&
|
if (op == ItDrawIndexOffset2 &&
|
||||||
length >= 5 &&
|
length >= 5 &&
|
||||||
TryReadUInt32(ctx, currentAddress + 4, out var indexCount) &&
|
TryReadUInt32(ctx, currentAddress + 4, out var indexCount) &&
|
||||||
indexCount != 0)
|
indexCount != 0)
|
||||||
{
|
{
|
||||||
sawIndexedDraw = true;
|
sawIndexedDraw = true;
|
||||||
|
TryTranslateGuestDraw(ctx, state, indexCount, ref guestDrawKind);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (op == ItNop &&
|
||||||
|
register == RDrawIndexAuto &&
|
||||||
|
length >= 2 &&
|
||||||
|
TryReadUInt32(ctx, currentAddress + 4, out var autoIndexCount) &&
|
||||||
|
autoIndexCount != 0)
|
||||||
|
{
|
||||||
|
sawIndexedDraw = true;
|
||||||
|
TryTranslateGuestDraw(ctx, state, autoIndexCount, ref guestDrawKind);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (op == ItNop && register == RFlip && length >= 6)
|
if (op == ItNop && register == RFlip && length >= 6)
|
||||||
@@ -654,6 +950,18 @@ public static class AgcExports
|
|||||||
unchecked((int)videoOutHandle),
|
unchecked((int)videoOutHandle),
|
||||||
displayBufferIndex);
|
displayBufferIndex);
|
||||||
}
|
}
|
||||||
|
else if (sawIndexedDraw &&
|
||||||
|
guestDrawKind != GuestDrawKind.None &&
|
||||||
|
VideoOutExports.TryGetDisplayBufferInfo(
|
||||||
|
unchecked((int)videoOutHandle),
|
||||||
|
displayBufferIndex,
|
||||||
|
out var displayBuffer))
|
||||||
|
{
|
||||||
|
VulkanVideoPresenter.SubmitGuestDraw(
|
||||||
|
guestDrawKind,
|
||||||
|
displayBuffer.Width,
|
||||||
|
displayBuffer.Height);
|
||||||
|
}
|
||||||
|
|
||||||
_ = VideoOutExports.SubmitFlipFromAgc(ctx, unchecked((int)videoOutHandle), displayBufferIndex, unchecked((int)flipMode), flipArg);
|
_ = VideoOutExports.SubmitFlipFromAgc(ctx, unchecked((int)videoOutHandle), displayBufferIndex, unchecked((int)flipMode), flipArg);
|
||||||
}
|
}
|
||||||
@@ -662,6 +970,120 @@ public static class AgcExports
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void ApplySubmittedRegisters(
|
||||||
|
CpuContext ctx,
|
||||||
|
SubmittedDcbState state,
|
||||||
|
ulong packetAddress,
|
||||||
|
uint packetLength,
|
||||||
|
uint op,
|
||||||
|
uint register)
|
||||||
|
{
|
||||||
|
if (op == ItSetShReg)
|
||||||
|
{
|
||||||
|
if (packetLength < 3 ||
|
||||||
|
!TryReadUInt32(ctx, packetAddress + sizeof(uint), out var startRegister))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint index = 0; index < packetLength - 2; index++)
|
||||||
|
{
|
||||||
|
if (!TryReadUInt32(
|
||||||
|
ctx,
|
||||||
|
packetAddress + 8 + ((ulong)index * sizeof(uint)),
|
||||||
|
out var value))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.ShRegisters[startRegister + index] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (op != ItNop ||
|
||||||
|
register is not (RCxRegsIndirect or RShRegsIndirect or RUcRegsIndirect) ||
|
||||||
|
packetLength < 4 ||
|
||||||
|
!TryReadUInt32(ctx, packetAddress + sizeof(uint), out var registerCount) ||
|
||||||
|
!TryReadUInt64(ctx, packetAddress + 8, out var registersAddress))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var destination = register switch
|
||||||
|
{
|
||||||
|
RCxRegsIndirect => state.CxRegisters,
|
||||||
|
RShRegsIndirect => state.ShRegisters,
|
||||||
|
_ => state.UcRegisters,
|
||||||
|
};
|
||||||
|
for (uint index = 0; index < registerCount; index++)
|
||||||
|
{
|
||||||
|
var entryAddress = registersAddress + ((ulong)index * 8);
|
||||||
|
if (!TryReadUInt32(ctx, entryAddress, out var registerOffset) ||
|
||||||
|
!TryReadUInt32(ctx, entryAddress + sizeof(uint), out var value))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (registerOffset != 0)
|
||||||
|
{
|
||||||
|
destination[registerOffset] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void TryTranslateGuestDraw(
|
||||||
|
CpuContext ctx,
|
||||||
|
SubmittedDcbState state,
|
||||||
|
uint vertexCount,
|
||||||
|
ref GuestDrawKind drawKind)
|
||||||
|
{
|
||||||
|
if (drawKind != GuestDrawKind.None ||
|
||||||
|
vertexCount != 3 ||
|
||||||
|
!TryGetShaderAddress(state.ShRegisters, SpiShaderPgmLoEs, SpiShaderPgmHiEs, out var exportShaderAddress) ||
|
||||||
|
!TryGetShaderAddress(state.ShRegisters, SpiShaderPgmLoPs, SpiShaderPgmHiPs, out var pixelShaderAddress) ||
|
||||||
|
!state.CxRegisters.TryGetValue(SpiPsInputEna, out var psInputEna) ||
|
||||||
|
!state.CxRegisters.TryGetValue(SpiPsInputAddr, out var psInputAddr) ||
|
||||||
|
!Gen5ShaderTranslator.TryTranslate(
|
||||||
|
ctx,
|
||||||
|
exportShaderAddress,
|
||||||
|
pixelShaderAddress,
|
||||||
|
psInputEna,
|
||||||
|
psInputAddr,
|
||||||
|
out drawKind))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (_submitTraceGate)
|
||||||
|
{
|
||||||
|
if (_tracedShaderTranslations.Add((exportShaderAddress, pixelShaderAddress, drawKind)))
|
||||||
|
{
|
||||||
|
TraceAgc(
|
||||||
|
$"agc.shader_translated kind={drawKind} es=0x{exportShaderAddress:X16} " +
|
||||||
|
$"ps=0x{pixelShaderAddress:X16} vertices={vertexCount}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetShaderAddress(
|
||||||
|
IReadOnlyDictionary<uint, uint> registers,
|
||||||
|
uint loRegister,
|
||||||
|
uint hiRegister,
|
||||||
|
out ulong address)
|
||||||
|
{
|
||||||
|
address = 0;
|
||||||
|
if (!registers.TryGetValue(loRegister, out var lo) ||
|
||||||
|
!registers.TryGetValue(hiRegister, out var hi))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
address = ((ulong)hi << 40) | ((ulong)lo << 8);
|
||||||
|
return address != 0;
|
||||||
|
}
|
||||||
|
|
||||||
private static bool TryReadTextureDescriptor(
|
private static bool TryReadTextureDescriptor(
|
||||||
CpuContext ctx,
|
CpuContext ctx,
|
||||||
ulong packetAddress,
|
ulong packetAddress,
|
||||||
@@ -802,6 +1224,7 @@ public static class AgcExports
|
|||||||
_softwarePresenterFingerprints[fingerprintKey] = fingerprint;
|
_softwarePresenterFingerprints[fingerprintKey] = fingerprint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VideoOutExports.SubmitHostRgbaFrame(sourceBytes, source.Width, source.Height);
|
||||||
TraceAgc(
|
TraceAgc(
|
||||||
$"agc.software_presenter src=0x{source.Address:X16} {source.Width}x{source.Height} fmt={source.Format} " +
|
$"agc.software_presenter src=0x{source.Address:X16} {source.Width}x{source.Height} fmt={source.Format} " +
|
||||||
$"dst=0x{destination.Address:X16} {destination.Width}x{destination.Height} fingerprint=0x{fingerprint:X16}");
|
$"dst=0x{destination.Address:X16} {destination.Width}x{destination.Height} fingerprint=0x{fingerprint:X16}");
|
||||||
@@ -895,8 +1318,20 @@ public static class AgcExports
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var expectedLo = shaderType == 2 ? SpiShaderPgmLoEs : shaderType == 1 ? SpiShaderPgmLoPs : 0;
|
var expectedLo = shaderType switch
|
||||||
var expectedHi = shaderType == 2 ? SpiShaderPgmHiEs : shaderType == 1 ? SpiShaderPgmHiPs : 0;
|
{
|
||||||
|
0 => ComputePgmLo,
|
||||||
|
1 => SpiShaderPgmLoPs,
|
||||||
|
2 => SpiShaderPgmLoEs,
|
||||||
|
_ => 0u,
|
||||||
|
};
|
||||||
|
var expectedHi = shaderType switch
|
||||||
|
{
|
||||||
|
0 => ComputePgmHi,
|
||||||
|
1 => SpiShaderPgmHiPs,
|
||||||
|
2 => SpiShaderPgmHiEs,
|
||||||
|
_ => 0u,
|
||||||
|
};
|
||||||
if (expectedLo == 0 || loRegister != expectedLo || hiRegister != expectedHi)
|
if (expectedLo == 0 || loRegister != expectedLo || hiRegister != expectedHi)
|
||||||
{
|
{
|
||||||
TraceCreateShader(0, headerAddress, codeAddress, $"unexpected-registers type={shaderType} lo=0x{loRegister:X8} hi=0x{hiRegister:X8}");
|
TraceCreateShader(0, headerAddress, codeAddress, $"unexpected-registers type={shaderType} lo=0x{loRegister:X8} hi=0x{hiRegister:X8}");
|
||||||
@@ -1031,6 +1466,150 @@ public static class AgcExports
|
|||||||
return ctx.TryWriteUInt64(fieldAddress, fieldAddress + relativeAddress);
|
return ctx.TryWriteUInt64(fieldAddress, fieldAddress + relativeAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int ReturnRegisterDefaults(CpuContext ctx, bool internalDefaults)
|
||||||
|
{
|
||||||
|
var version = (uint)ctx[CpuRegister.Rdi];
|
||||||
|
if (!IsSupportedRegisterDefaultsVersion(version))
|
||||||
|
{
|
||||||
|
return ReturnPointer(ctx, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryGetRegisterDefaultsAllocation(ctx, out var allocation))
|
||||||
|
{
|
||||||
|
return ReturnPointer(ctx, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
var address = internalDefaults ? allocation.Internal : allocation.Primary;
|
||||||
|
TraceAgc($"agc.get_register_defaults internal={internalDefaults} version={version} address=0x{address:X16}");
|
||||||
|
return ReturnPointer(ctx, address);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsSupportedRegisterDefaultsVersion(uint version)
|
||||||
|
{
|
||||||
|
return version is RegisterDefaultsVersion7 or RegisterDefaultsVersion8;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetRegisterDefaultsAllocation(
|
||||||
|
CpuContext ctx,
|
||||||
|
out RegisterDefaultsAllocation allocation)
|
||||||
|
{
|
||||||
|
lock (_registerDefaultsGate)
|
||||||
|
{
|
||||||
|
if (_registerDefaultsAllocations.TryGetValue(ctx.Memory, out allocation!))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryBuildRegisterDefaults(
|
||||||
|
ctx,
|
||||||
|
PrimaryRegisterDefaults,
|
||||||
|
cxTableLength: 78,
|
||||||
|
shTableLength: 29,
|
||||||
|
ucTableLength: 20,
|
||||||
|
out var primaryAddress) ||
|
||||||
|
!TryBuildRegisterDefaults(
|
||||||
|
ctx,
|
||||||
|
InternalRegisterDefaults,
|
||||||
|
cxTableLength: 4,
|
||||||
|
shTableLength: 15,
|
||||||
|
ucTableLength: 3,
|
||||||
|
out var internalAddress))
|
||||||
|
{
|
||||||
|
allocation = null!;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
allocation = new RegisterDefaultsAllocation(primaryAddress, internalAddress);
|
||||||
|
_registerDefaultsAllocations.Add(ctx.Memory, allocation);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryBuildRegisterDefaults(
|
||||||
|
CpuContext ctx,
|
||||||
|
RegisterDefaultGroup[] groups,
|
||||||
|
int cxTableLength,
|
||||||
|
int shTableLength,
|
||||||
|
int ucTableLength,
|
||||||
|
out ulong address)
|
||||||
|
{
|
||||||
|
var cxTableOffset = AlignUp(RegisterDefaultsSize, sizeof(ulong));
|
||||||
|
var shTableOffset = cxTableOffset + (cxTableLength * sizeof(ulong));
|
||||||
|
var ucTableOffset = shTableOffset + (shTableLength * sizeof(ulong));
|
||||||
|
var typesOffset = AlignUp(ucTableOffset + (ucTableLength * sizeof(ulong)), sizeof(uint));
|
||||||
|
var registerBlocksOffset = AlignUp(typesOffset + (groups.Length * 3 * sizeof(uint)), sizeof(ulong));
|
||||||
|
var blobLength = registerBlocksOffset + (groups.Length * RegisterDefaultBlockSize);
|
||||||
|
|
||||||
|
if (!KernelMemoryCompatExports.TryAllocateHleData(ctx, (ulong)blobLength, 0x1000, out address))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var blob = new byte[blobLength];
|
||||||
|
WriteBlobUInt64(blob, 0x00, address + (ulong)cxTableOffset);
|
||||||
|
WriteBlobUInt64(blob, 0x08, address + (ulong)shTableOffset);
|
||||||
|
WriteBlobUInt64(blob, 0x10, address + (ulong)ucTableOffset);
|
||||||
|
WriteBlobUInt64(blob, 0x30, address + (ulong)typesOffset);
|
||||||
|
WriteBlobUInt32(blob, 0x38, (uint)groups.Length);
|
||||||
|
|
||||||
|
for (var groupIndex = 0; groupIndex < groups.Length; groupIndex++)
|
||||||
|
{
|
||||||
|
var group = groups[groupIndex];
|
||||||
|
if (group.Registers.Length > 16)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tableOffset = group.Space switch
|
||||||
|
{
|
||||||
|
0 => cxTableOffset,
|
||||||
|
1 => shTableOffset,
|
||||||
|
2 => ucTableOffset,
|
||||||
|
_ => -1,
|
||||||
|
};
|
||||||
|
var tableLength = group.Space switch
|
||||||
|
{
|
||||||
|
0 => cxTableLength,
|
||||||
|
1 => shTableLength,
|
||||||
|
2 => ucTableLength,
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
if (tableOffset < 0 || group.Index >= tableLength)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var registerBlockOffset = registerBlocksOffset + (groupIndex * RegisterDefaultBlockSize);
|
||||||
|
WriteBlobUInt64(
|
||||||
|
blob,
|
||||||
|
tableOffset + ((int)group.Index * sizeof(ulong)),
|
||||||
|
address + (ulong)registerBlockOffset);
|
||||||
|
|
||||||
|
var typeEntryOffset = typesOffset + (groupIndex * 3 * sizeof(uint));
|
||||||
|
WriteBlobUInt32(blob, typeEntryOffset, group.Type);
|
||||||
|
WriteBlobUInt32(blob, typeEntryOffset + sizeof(uint), (group.Index * 4) + group.Space);
|
||||||
|
|
||||||
|
for (var registerIndex = 0; registerIndex < group.Registers.Length; registerIndex++)
|
||||||
|
{
|
||||||
|
var register = group.Registers[registerIndex];
|
||||||
|
var registerOffset = registerBlockOffset + (registerIndex * 2 * sizeof(uint));
|
||||||
|
WriteBlobUInt32(blob, registerOffset, register.Offset);
|
||||||
|
WriteBlobUInt32(blob, registerOffset + sizeof(uint), register.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.Memory.TryWrite(address, blob);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int AlignUp(int value, int alignment) =>
|
||||||
|
(value + alignment - 1) & -alignment;
|
||||||
|
|
||||||
|
private static void WriteBlobUInt32(Span<byte> blob, int offset, uint value) =>
|
||||||
|
BinaryPrimitives.WriteUInt32LittleEndian(blob[offset..], value);
|
||||||
|
|
||||||
|
private static void WriteBlobUInt64(Span<byte> blob, int offset, ulong value) =>
|
||||||
|
BinaryPrimitives.WriteUInt64LittleEndian(blob[offset..], value);
|
||||||
|
|
||||||
private static int ReturnPointer(CpuContext ctx, ulong pointer)
|
private static int ReturnPointer(CpuContext ctx, ulong pointer)
|
||||||
{
|
{
|
||||||
ctx[CpuRegister.Rax] = pointer;
|
ctx[CpuRegister.Rax] = pointer;
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
using SharpEmu.HLE;
|
||||||
|
using SharpEmu.Libs.VideoOut;
|
||||||
|
using System.Buffers.Binary;
|
||||||
|
|
||||||
|
namespace SharpEmu.Libs.Agc;
|
||||||
|
|
||||||
|
internal static class Gen5ShaderTranslator
|
||||||
|
{
|
||||||
|
private static readonly uint[] FullscreenBarycentricEs =
|
||||||
|
[
|
||||||
|
0xBFA00001, 0x7E000000, 0x7E000000, 0x7E000000,
|
||||||
|
0x93EBFF03, 0x00080008, 0x8F6A8C6B, 0x8700FF03,
|
||||||
|
0x000000FF, 0x887C6A00, 0xBF900009, 0x81EA6BC0,
|
||||||
|
0x90FE6AC1, 0xF8000941, 0x00000000, 0x81EA00C0,
|
||||||
|
0xBF8CFF0F, 0x90FE6AC1, 0x36040A81, 0x2C060A81,
|
||||||
|
0x7E000280, 0x7E0202F2, 0xD7460002, 0x03050302,
|
||||||
|
0xD7460003, 0x03050303, 0x7E040B02, 0x7E060B03,
|
||||||
|
0xF80008CF, 0x01000302, 0xBF810000,
|
||||||
|
];
|
||||||
|
|
||||||
|
private static readonly uint[] FullscreenBarycentricPs =
|
||||||
|
[
|
||||||
|
0xD52F0000, 0x00000200,
|
||||||
|
0xD52F0001, 0x00000602,
|
||||||
|
0xF8001C0F, 0x00000100,
|
||||||
|
0xBF810000,
|
||||||
|
];
|
||||||
|
|
||||||
|
public static bool TryTranslate(
|
||||||
|
CpuContext ctx,
|
||||||
|
ulong exportShaderAddress,
|
||||||
|
ulong pixelShaderAddress,
|
||||||
|
uint psInputEna,
|
||||||
|
uint psInputAddr,
|
||||||
|
out GuestDrawKind drawKind)
|
||||||
|
{
|
||||||
|
drawKind = GuestDrawKind.None;
|
||||||
|
if (exportShaderAddress == 0 ||
|
||||||
|
pixelShaderAddress == 0 ||
|
||||||
|
psInputEna != 0x00000002 ||
|
||||||
|
psInputAddr != 0x00000002 ||
|
||||||
|
!MatchesProgram(ctx, exportShaderAddress, FullscreenBarycentricEs) ||
|
||||||
|
!MatchesProgram(ctx, pixelShaderAddress, FullscreenBarycentricPs))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
drawKind = GuestDrawKind.FullscreenBarycentric;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool MatchesProgram(CpuContext ctx, ulong address, ReadOnlySpan<uint> expected)
|
||||||
|
{
|
||||||
|
var bytes = new byte[expected.Length * sizeof(uint)];
|
||||||
|
if (!ctx.Memory.TryRead(address, bytes))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var index = 0; index < expected.Length; index++)
|
||||||
|
{
|
||||||
|
if (BinaryPrimitives.ReadUInt32LittleEndian(bytes.AsSpan(index * sizeof(uint))) != expected[index])
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,6 +44,36 @@ public static class VideoOutExports
|
|||||||
private static int _nextHandle = 1;
|
private static int _nextHandle = 1;
|
||||||
private static int _frameDumpCount;
|
private static int _frameDumpCount;
|
||||||
private static long _nextFrameDumpIndex;
|
private static long _nextFrameDumpIndex;
|
||||||
|
private static string _windowTitle = "SharpEmu VideoOut";
|
||||||
|
|
||||||
|
public static void ConfigureApplicationInfo(string? title, string? titleId, string? version)
|
||||||
|
{
|
||||||
|
var parts = new List<string>();
|
||||||
|
if (!string.IsNullOrWhiteSpace(title))
|
||||||
|
{
|
||||||
|
parts.Add(title.Trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(titleId))
|
||||||
|
{
|
||||||
|
parts.Add($"[{titleId.Trim()}]");
|
||||||
|
}
|
||||||
|
|
||||||
|
var application = parts.Count == 0 ? "VideoOut" : string.Join(' ', parts);
|
||||||
|
var versionSuffix = string.IsNullOrWhiteSpace(version) ? string.Empty : $" v{version.Trim()}";
|
||||||
|
lock (_stateGate)
|
||||||
|
{
|
||||||
|
_windowTitle = $"SharpEmu - {application}{versionSuffix}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string GetWindowTitle()
|
||||||
|
{
|
||||||
|
lock (_stateGate)
|
||||||
|
{
|
||||||
|
return _windowTitle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private sealed class VideoOutPortState
|
private sealed class VideoOutPortState
|
||||||
{
|
{
|
||||||
@@ -210,7 +240,15 @@ public static class VideoOutExports
|
|||||||
|
|
||||||
lock (_stateGate)
|
lock (_stateGate)
|
||||||
{
|
{
|
||||||
port.FlipEvents.Add(new FlipEventRegistration(equeue, userData));
|
var existingIndex = port.FlipEvents.FindIndex(registration => registration.Equeue == equeue);
|
||||||
|
if (existingIndex >= 0)
|
||||||
|
{
|
||||||
|
port.FlipEvents[existingIndex] = new FlipEventRegistration(equeue, userData);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
port.FlipEvents.Add(new FlipEventRegistration(equeue, userData));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TraceVideoOut($"videoout.add_flip_event eq=0x{equeue:X16} handle={handle} udata=0x{userData:X16}");
|
TraceVideoOut($"videoout.add_flip_event eq=0x{equeue:X16} handle={handle} udata=0x{userData:X16}");
|
||||||
@@ -284,7 +322,8 @@ public static class VideoOutExports
|
|||||||
return OrbisVideoOutErrorInvalidEvent;
|
return OrbisVideoOutErrorInvalidEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.TryWriteUInt64(dataAddress, data >> 16)
|
var decodedData = unchecked((ulong)(unchecked((long)data) >> 16));
|
||||||
|
return ctx.TryWriteUInt64(dataAddress, decodedData)
|
||||||
? (int)OrbisGen2Result.ORBIS_GEN2_OK
|
? (int)OrbisGen2Result.ORBIS_GEN2_OK
|
||||||
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||||
}
|
}
|
||||||
@@ -610,7 +649,7 @@ public static class VideoOutExports
|
|||||||
return OrbisVideoOutErrorInvalidIndex;
|
return OrbisVideoOutErrorInvalidIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong eventData;
|
ulong eventHint;
|
||||||
List<FlipEventRegistration> flipEvents;
|
List<FlipEventRegistration> flipEvents;
|
||||||
lock (_stateGate)
|
lock (_stateGate)
|
||||||
{
|
{
|
||||||
@@ -621,9 +660,8 @@ public static class VideoOutExports
|
|||||||
|
|
||||||
port.CurrentBuffer = bufferIndex;
|
port.CurrentBuffer = bufferIndex;
|
||||||
port.FlipCount++;
|
port.FlipCount++;
|
||||||
var eventCount = Math.Min(port.FlipCount, 0xFUL);
|
eventHint = SceVideoOutInternalEventFlip |
|
||||||
var timeBits = (ulong)Environment.TickCount64 & 0xFFFUL;
|
((unchecked((ulong)flipArg) & 0x0000_FFFF_FFFF_FFFFUL) << 16);
|
||||||
eventData = timeBits | (eventCount << 12) | ((unchecked((ulong)flipArg) & 0x0000_FFFF_FFFF_FFFFUL) << 16);
|
|
||||||
flipEvents = new List<FlipEventRegistration>(port.FlipEvents);
|
flipEvents = new List<FlipEventRegistration>(port.FlipEvents);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -637,15 +675,12 @@ public static class VideoOutExports
|
|||||||
|
|
||||||
foreach (var flipEvent in flipEvents)
|
foreach (var flipEvent in flipEvents)
|
||||||
{
|
{
|
||||||
_ = KernelEventQueueCompatExports.EnqueueEvent(
|
_ = KernelEventQueueCompatExports.TriggerDisplayEvent(
|
||||||
flipEvent.Equeue,
|
flipEvent.Equeue,
|
||||||
new KernelEventQueueCompatExports.KernelQueuedEvent(
|
SceVideoOutInternalEventFlip,
|
||||||
SceVideoOutInternalEventFlip,
|
OrbisKernelEventFilterVideoOut,
|
||||||
OrbisKernelEventFilterVideoOut,
|
eventHint,
|
||||||
0,
|
flipEvent.UserData);
|
||||||
0,
|
|
||||||
eventData,
|
|
||||||
flipEvent.UserData));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TraceVideoOut($"videoout.submit_flip handle={handle} index={bufferIndex} mode={flipMode} arg={flipArg} events={flipEvents.Count}");
|
TraceVideoOut($"videoout.submit_flip handle={handle} index={bufferIndex} mode={flipMode} arg={flipArg} events={flipEvents.Count}");
|
||||||
|
|||||||
@@ -12,14 +12,17 @@ using VkSemaphore = Silk.NET.Vulkan.Semaphore;
|
|||||||
|
|
||||||
namespace SharpEmu.Libs.VideoOut;
|
namespace SharpEmu.Libs.VideoOut;
|
||||||
|
|
||||||
|
internal enum GuestDrawKind
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
FullscreenBarycentric,
|
||||||
|
}
|
||||||
|
|
||||||
internal static unsafe class VulkanVideoPresenter
|
internal static unsafe class VulkanVideoPresenter
|
||||||
{
|
{
|
||||||
private static readonly object _gate = new();
|
private static readonly object _gate = new();
|
||||||
private static Thread? _thread;
|
private static Thread? _thread;
|
||||||
private static byte[]? _latestFrame;
|
private static Presentation? _latestPresentation;
|
||||||
private static uint _latestWidth;
|
|
||||||
private static uint _latestHeight;
|
|
||||||
private static long _latestSequence;
|
|
||||||
private static bool _closed;
|
private static bool _closed;
|
||||||
|
|
||||||
public static void Submit(byte[] bgraFrame, uint width, uint height)
|
public static void Submit(byte[] bgraFrame, uint width, uint height)
|
||||||
@@ -36,10 +39,42 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_latestFrame = bgraFrame;
|
var sequence = (_latestPresentation?.Sequence ?? 0) + 1;
|
||||||
_latestWidth = width;
|
_latestPresentation = new Presentation(bgraFrame, width, height, sequence, GuestDrawKind.None);
|
||||||
_latestHeight = height;
|
if (_thread is not null)
|
||||||
_latestSequence++;
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_thread = new Thread(Run)
|
||||||
|
{
|
||||||
|
IsBackground = true,
|
||||||
|
Name = "SharpEmu Vulkan VideoOut",
|
||||||
|
};
|
||||||
|
_thread.Start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SubmitGuestDraw(GuestDrawKind drawKind, uint width, uint height)
|
||||||
|
{
|
||||||
|
if (drawKind == GuestDrawKind.None || width == 0 || height == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (_gate)
|
||||||
|
{
|
||||||
|
if (_closed ||
|
||||||
|
_latestPresentation is { Pixels: null } latest &&
|
||||||
|
latest.DrawKind == drawKind &&
|
||||||
|
latest.Width == width &&
|
||||||
|
latest.Height == height)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sequence = (_latestPresentation?.Sequence ?? 0) + 1;
|
||||||
|
_latestPresentation = new Presentation(null, width, height, sequence, drawKind);
|
||||||
if (_thread is not null)
|
if (_thread is not null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -60,8 +95,8 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
uint height;
|
uint height;
|
||||||
lock (_gate)
|
lock (_gate)
|
||||||
{
|
{
|
||||||
width = _latestWidth;
|
width = _latestPresentation?.Width ?? 1280;
|
||||||
height = _latestHeight;
|
height = _latestPresentation?.Height ?? 720;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -83,25 +118,36 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryTakeFrame(long presentedSequence, out Frame frame)
|
private static bool TryTakePresentation(long presentedSequence, out Presentation presentation)
|
||||||
{
|
{
|
||||||
lock (_gate)
|
lock (_gate)
|
||||||
{
|
{
|
||||||
if (_latestFrame is null || _latestSequence == presentedSequence)
|
if (_latestPresentation is not { } latest || latest.Sequence == presentedSequence)
|
||||||
{
|
{
|
||||||
frame = default;
|
presentation = default;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
frame = new Frame(_latestFrame, _latestWidth, _latestHeight, _latestSequence);
|
presentation = latest;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly record struct Frame(byte[] Pixels, uint Width, uint Height, long Sequence);
|
private readonly record struct Presentation(
|
||||||
|
byte[]? Pixels,
|
||||||
|
uint Width,
|
||||||
|
uint Height,
|
||||||
|
long Sequence,
|
||||||
|
GuestDrawKind DrawKind);
|
||||||
|
|
||||||
private sealed class Presenter : IDisposable
|
private sealed class Presenter : IDisposable
|
||||||
{
|
{
|
||||||
|
private const string FullscreenBarycentricVertexSpirv =
|
||||||
|
"AwIjBwAAAQALAAgAMgAAAAAAAAARAAIAAQAAAAsABgABAAAAR0xTTC5zdGQuNDUwAAAAAA4AAwAAAAAAAQAAAA8ACAAAAAAABAAAAG1haW4AAAAADQAAABoAAAApAAAAAwADAAIAAADCAQAABQAEAAQAAABtYWluAAAAAAUABgALAAAAZ2xfUGVyVmVydGV4AAAAAAYABgALAAAAAAAAAGdsX1Bvc2l0aW9uAAYABwALAAAAAQAAAGdsX1BvaW50U2l6ZQAAAAAGAAcACwAAAAIAAABnbF9DbGlwRGlzdGFuY2UABgAHAAsAAAADAAAAZ2xfQ3VsbERpc3RhbmNlAAUAAwANAAAAAAAAAAUABgAaAAAAZ2xfVmVydGV4SW5kZXgAAAUABQAdAAAAaW5kZXhhYmxlAAAABQAFACkAAABiYXJ5Y2VudHJpYwAFAAUALwAAAGluZGV4YWJsZQAAAEcAAwALAAAAAgAAAEgABQALAAAAAAAAAAsAAAAAAAAASAAFAAsAAAABAAAACwAAAAEAAABIAAUACwAAAAIAAAALAAAAAwAAAEgABQALAAAAAwAAAAsAAAAEAAAARwAEABoAAAALAAAAKgAAAEcABAApAAAAHgAAAAAAAAATAAIAAgAAACEAAwADAAAAAgAAABYAAwAGAAAAIAAAABcABAAHAAAABgAAAAQAAAAVAAQACAAAACAAAAAAAAAAKwAEAAgAAAAJAAAAAQAAABwABAAKAAAABgAAAAkAAAAeAAYACwAAAAcAAAAGAAAACgAAAAoAAAAgAAQADAAAAAMAAAALAAAAOwAEAAwAAAANAAAAAwAAABUABAAOAAAAIAAAAAEAAAArAAQADgAAAA8AAAAAAAAAFwAEABAAAAAGAAAAAgAAACsABAAIAAAAEQAAAAMAAAAcAAQAEgAAABAAAAARAAAAKwAEAAYAAAATAAAAAACAvywABQAQAAAAFAAAABMAAAATAAAAKwAEAAYAAAAVAAAAAABAQCwABQAQAAAAFgAAABUAAAATAAAALAAFABAAAAAXAAAAEwAAABUAAAAsAAYAEgAAABgAAAAUAAAAFgAAABcAAAAgAAQAGQAAAAEAAAAOAAAAOwAEABkAAAAaAAAAAQAAACAABAAcAAAABwAAABIAAAAgAAQAHgAAAAcAAAAQAAAAKwAEAAYAAAAhAAAAAAAAACsABAAGAAAAIgAAAAAAgD8gAAQAJgAAAAMAAAAHAAAAIAAEACgAAAADAAAAEAAAADsABAAoAAAAKQAAAAMAAAAsAAUAEAAAACoAAAAiAAAAIQAAACwABQAQAAAAKwAAACEAAAAiAAAALAAFABAAAAAsAAAAIQAAACEAAAAsAAYAEgAAAC0AAAAqAAAAKwAAACwAAAA2AAUAAgAAAAQAAAAAAAAAAwAAAPgAAgAFAAAAOwAEABwAAAAdAAAABwAAADsABAAcAAAALwAAAAcAAAA9AAQADgAAABsAAAAaAAAAPgADAB0AAAAYAAAAQQAFAB4AAAAfAAAAHQAAABsAAAA9AAQAEAAAACAAAAAfAAAAUQAFAAYAAAAjAAAAIAAAAAAAAABRAAUABgAAACQAAAAgAAAAAQAAAFAABwAHAAAAJQAAACMAAAAkAAAAIQAAACIAAABBAAUAJgAAACcAAAANAAAADwAAAD4AAwAnAAAAJQAAAD0ABAAOAAAALgAAABoAAAA+AAMALwAAAC0AAABBAAUAHgAAADAAAAAvAAAALgAAAD0ABAAQAAAAMQAAADAAAAA+AAMAKQAAADEAAAD9AAEAOAABAA==";
|
||||||
|
|
||||||
|
private const string FullscreenBarycentricFragmentSpirv =
|
||||||
|
"AwIjBwAAAQALAAgAEgAAAAAAAAARAAIAAQAAAAsABgABAAAAR0xTTC5zdGQuNDUwAAAAAA4AAwAAAAAAAQAAAA8ABwAEAAAABAAAAG1haW4AAAAACQAAAAwAAAAQAAMABAAAAAcAAAADAAMAAgAAAMIBAAAFAAQABAAAAG1haW4AAAAABQAFAAkAAABvdXRDb2xvcgAAAAAFAAUADAAAAGJhcnljZW50cmljAEcABAAJAAAAHgAAAAAAAABHAAQADAAAAB4AAAAAAAAAEwACAAIAAAAhAAMAAwAAAAIAAAAWAAMABgAAACAAAAAXAAQABwAAAAYAAAAEAAAAIAAEAAgAAAADAAAABwAAADsABAAIAAAACQAAAAMAAAAXAAQACgAAAAYAAAACAAAAIAAEAAsAAAABAAAACgAAADsABAALAAAADAAAAAEAAAArAAQABgAAAA4AAAAAAAAANgAFAAIAAAAEAAAAAAAAAAMAAAD4AAIABQAAAD0ABAAKAAAADQAAAAwAAABRAAUABgAAAA8AAAANAAAAAAAAAFEABQAGAAAAEAAAAA0AAAABAAAAUAAHAAcAAAARAAAADwAAABAAAAAOAAAADgAAAD4AAwAJAAAAEQAAAP0AAQA4AAEA";
|
||||||
|
|
||||||
private readonly IWindow _window;
|
private readonly IWindow _window;
|
||||||
private Vk _vk = null!;
|
private Vk _vk = null!;
|
||||||
private KhrSurface _surfaceApi = null!;
|
private KhrSurface _surfaceApi = null!;
|
||||||
@@ -114,9 +160,14 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
private uint _queueFamilyIndex;
|
private uint _queueFamilyIndex;
|
||||||
private SwapchainKHR _swapchain;
|
private SwapchainKHR _swapchain;
|
||||||
private Image[] _swapchainImages = [];
|
private Image[] _swapchainImages = [];
|
||||||
|
private ImageView[] _swapchainImageViews = [];
|
||||||
|
private Framebuffer[] _framebuffers = [];
|
||||||
private bool[] _imageInitialized = [];
|
private bool[] _imageInitialized = [];
|
||||||
private Format _swapchainFormat;
|
private Format _swapchainFormat;
|
||||||
private Extent2D _extent;
|
private Extent2D _extent;
|
||||||
|
private RenderPass _renderPass;
|
||||||
|
private PipelineLayout _pipelineLayout;
|
||||||
|
private Pipeline _barycentricPipeline;
|
||||||
private CommandPool _commandPool;
|
private CommandPool _commandPool;
|
||||||
private CommandBuffer _commandBuffer;
|
private CommandBuffer _commandBuffer;
|
||||||
private VkSemaphore _imageAvailable;
|
private VkSemaphore _imageAvailable;
|
||||||
@@ -127,12 +178,13 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
private long _presentedSequence;
|
private long _presentedSequence;
|
||||||
private bool _vulkanReady;
|
private bool _vulkanReady;
|
||||||
private bool _firstFramePresented;
|
private bool _firstFramePresented;
|
||||||
|
private bool _firstGuestDrawPresented;
|
||||||
|
|
||||||
public Presenter(uint width, uint height)
|
public Presenter(uint width, uint height)
|
||||||
{
|
{
|
||||||
var options = WindowOptions.DefaultVulkan;
|
var options = WindowOptions.DefaultVulkan;
|
||||||
options.Size = new Vector2D<int>((int)width, (int)height);
|
options.Size = new Vector2D<int>((int)width, (int)height);
|
||||||
options.Title = "SharpEmu VideoOut";
|
options.Title = VideoOutExports.GetWindowTitle();
|
||||||
options.WindowBorder = WindowBorder.Fixed;
|
options.WindowBorder = WindowBorder.Fixed;
|
||||||
options.VSync = true;
|
options.VSync = true;
|
||||||
_window = Window.Create(options);
|
_window = Window.Create(options);
|
||||||
@@ -158,6 +210,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
CreateDevice();
|
CreateDevice();
|
||||||
CreateSwapchain();
|
CreateSwapchain();
|
||||||
CreateCommandResources();
|
CreateCommandResources();
|
||||||
|
CreateGuestDrawResources();
|
||||||
_vulkanReady = true;
|
_vulkanReady = true;
|
||||||
Console.Error.WriteLine(
|
Console.Error.WriteLine(
|
||||||
$"[LOADER][INFO] Vulkan VideoOut ready: {_extent.Width}x{_extent.Height}, format={_swapchainFormat}");
|
$"[LOADER][INFO] Vulkan VideoOut ready: {_extent.Width}x{_extent.Height}, format={_swapchainFormat}");
|
||||||
@@ -323,7 +376,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
ImageColorSpace = surfaceFormat.ColorSpace,
|
ImageColorSpace = surfaceFormat.ColorSpace,
|
||||||
ImageExtent = _extent,
|
ImageExtent = _extent,
|
||||||
ImageArrayLayers = 1,
|
ImageArrayLayers = 1,
|
||||||
ImageUsage = ImageUsageFlags.TransferDstBit,
|
ImageUsage = ImageUsageFlags.TransferDstBit | ImageUsageFlags.ColorAttachmentBit,
|
||||||
ImageSharingMode = SharingMode.Exclusive,
|
ImageSharingMode = SharingMode.Exclusive,
|
||||||
PreTransform = capabilities.CurrentTransform,
|
PreTransform = capabilities.CurrentTransform,
|
||||||
CompositeAlpha = compositeAlpha,
|
CompositeAlpha = compositeAlpha,
|
||||||
@@ -377,6 +430,218 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
CreateStagingBuffer((ulong)_extent.Width * _extent.Height * 4);
|
CreateStagingBuffer((ulong)_extent.Width * _extent.Height * 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void CreateGuestDrawResources()
|
||||||
|
{
|
||||||
|
var colorAttachment = new AttachmentDescription
|
||||||
|
{
|
||||||
|
Format = _swapchainFormat,
|
||||||
|
Samples = SampleCountFlags.Count1Bit,
|
||||||
|
LoadOp = AttachmentLoadOp.Clear,
|
||||||
|
StoreOp = AttachmentStoreOp.Store,
|
||||||
|
StencilLoadOp = AttachmentLoadOp.DontCare,
|
||||||
|
StencilStoreOp = AttachmentStoreOp.DontCare,
|
||||||
|
InitialLayout = ImageLayout.Undefined,
|
||||||
|
FinalLayout = ImageLayout.PresentSrcKhr,
|
||||||
|
};
|
||||||
|
var colorReference = new AttachmentReference
|
||||||
|
{
|
||||||
|
Attachment = 0,
|
||||||
|
Layout = ImageLayout.ColorAttachmentOptimal,
|
||||||
|
};
|
||||||
|
var subpass = new SubpassDescription
|
||||||
|
{
|
||||||
|
PipelineBindPoint = PipelineBindPoint.Graphics,
|
||||||
|
ColorAttachmentCount = 1,
|
||||||
|
PColorAttachments = &colorReference,
|
||||||
|
};
|
||||||
|
var dependency = new SubpassDependency
|
||||||
|
{
|
||||||
|
SrcSubpass = Vk.SubpassExternal,
|
||||||
|
DstSubpass = 0,
|
||||||
|
SrcStageMask = PipelineStageFlags.ColorAttachmentOutputBit,
|
||||||
|
DstStageMask = PipelineStageFlags.ColorAttachmentOutputBit,
|
||||||
|
DstAccessMask = AccessFlags.ColorAttachmentWriteBit,
|
||||||
|
};
|
||||||
|
var renderPassInfo = new RenderPassCreateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.RenderPassCreateInfo,
|
||||||
|
AttachmentCount = 1,
|
||||||
|
PAttachments = &colorAttachment,
|
||||||
|
SubpassCount = 1,
|
||||||
|
PSubpasses = &subpass,
|
||||||
|
DependencyCount = 1,
|
||||||
|
PDependencies = &dependency,
|
||||||
|
};
|
||||||
|
Check(_vk.CreateRenderPass(_device, &renderPassInfo, null, out _renderPass), "vkCreateRenderPass");
|
||||||
|
|
||||||
|
_swapchainImageViews = new ImageView[_swapchainImages.Length];
|
||||||
|
_framebuffers = new Framebuffer[_swapchainImages.Length];
|
||||||
|
for (var index = 0; index < _swapchainImages.Length; index++)
|
||||||
|
{
|
||||||
|
var viewInfo = new ImageViewCreateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.ImageViewCreateInfo,
|
||||||
|
Image = _swapchainImages[index],
|
||||||
|
ViewType = ImageViewType.Type2D,
|
||||||
|
Format = _swapchainFormat,
|
||||||
|
Components = new ComponentMapping(
|
||||||
|
ComponentSwizzle.Identity,
|
||||||
|
ComponentSwizzle.Identity,
|
||||||
|
ComponentSwizzle.Identity,
|
||||||
|
ComponentSwizzle.Identity),
|
||||||
|
SubresourceRange = ColorSubresourceRange(),
|
||||||
|
};
|
||||||
|
Check(
|
||||||
|
_vk.CreateImageView(_device, &viewInfo, null, out _swapchainImageViews[index]),
|
||||||
|
"vkCreateImageView");
|
||||||
|
|
||||||
|
var imageView = _swapchainImageViews[index];
|
||||||
|
var framebufferInfo = new FramebufferCreateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.FramebufferCreateInfo,
|
||||||
|
RenderPass = _renderPass,
|
||||||
|
AttachmentCount = 1,
|
||||||
|
PAttachments = &imageView,
|
||||||
|
Width = _extent.Width,
|
||||||
|
Height = _extent.Height,
|
||||||
|
Layers = 1,
|
||||||
|
};
|
||||||
|
Check(
|
||||||
|
_vk.CreateFramebuffer(_device, &framebufferInfo, null, out _framebuffers[index]),
|
||||||
|
"vkCreateFramebuffer");
|
||||||
|
}
|
||||||
|
|
||||||
|
var layoutInfo = new PipelineLayoutCreateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.PipelineLayoutCreateInfo,
|
||||||
|
};
|
||||||
|
Check(
|
||||||
|
_vk.CreatePipelineLayout(_device, &layoutInfo, null, out _pipelineLayout),
|
||||||
|
"vkCreatePipelineLayout");
|
||||||
|
CreateBarycentricPipeline();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateBarycentricPipeline()
|
||||||
|
{
|
||||||
|
var vertexBytes = Convert.FromBase64String(FullscreenBarycentricVertexSpirv);
|
||||||
|
var fragmentBytes = Convert.FromBase64String(FullscreenBarycentricFragmentSpirv);
|
||||||
|
var vertexModule = CreateShaderModule(vertexBytes);
|
||||||
|
var fragmentModule = CreateShaderModule(fragmentBytes);
|
||||||
|
var entryPoint = (byte*)SilkMarshal.StringToPtr("main");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var shaderStages = stackalloc PipelineShaderStageCreateInfo[2];
|
||||||
|
shaderStages[0] = new PipelineShaderStageCreateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.PipelineShaderStageCreateInfo,
|
||||||
|
Stage = ShaderStageFlags.VertexBit,
|
||||||
|
Module = vertexModule,
|
||||||
|
PName = entryPoint,
|
||||||
|
};
|
||||||
|
shaderStages[1] = new PipelineShaderStageCreateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.PipelineShaderStageCreateInfo,
|
||||||
|
Stage = ShaderStageFlags.FragmentBit,
|
||||||
|
Module = fragmentModule,
|
||||||
|
PName = entryPoint,
|
||||||
|
};
|
||||||
|
|
||||||
|
var vertexInput = new PipelineVertexInputStateCreateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.PipelineVertexInputStateCreateInfo,
|
||||||
|
};
|
||||||
|
var inputAssembly = new PipelineInputAssemblyStateCreateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.PipelineInputAssemblyStateCreateInfo,
|
||||||
|
Topology = PrimitiveTopology.TriangleList,
|
||||||
|
};
|
||||||
|
var viewport = new Viewport(0, 0, _extent.Width, _extent.Height, 0, 1);
|
||||||
|
var scissor = new Rect2D(new Offset2D(0, 0), _extent);
|
||||||
|
var viewportState = new PipelineViewportStateCreateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.PipelineViewportStateCreateInfo,
|
||||||
|
ViewportCount = 1,
|
||||||
|
PViewports = &viewport,
|
||||||
|
ScissorCount = 1,
|
||||||
|
PScissors = &scissor,
|
||||||
|
};
|
||||||
|
var rasterization = new PipelineRasterizationStateCreateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.PipelineRasterizationStateCreateInfo,
|
||||||
|
PolygonMode = PolygonMode.Fill,
|
||||||
|
CullMode = CullModeFlags.None,
|
||||||
|
FrontFace = FrontFace.CounterClockwise,
|
||||||
|
LineWidth = 1,
|
||||||
|
};
|
||||||
|
var multisample = new PipelineMultisampleStateCreateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.PipelineMultisampleStateCreateInfo,
|
||||||
|
RasterizationSamples = SampleCountFlags.Count1Bit,
|
||||||
|
};
|
||||||
|
var colorBlendAttachment = new PipelineColorBlendAttachmentState
|
||||||
|
{
|
||||||
|
ColorWriteMask =
|
||||||
|
ColorComponentFlags.RBit |
|
||||||
|
ColorComponentFlags.GBit |
|
||||||
|
ColorComponentFlags.BBit |
|
||||||
|
ColorComponentFlags.ABit,
|
||||||
|
};
|
||||||
|
var colorBlend = new PipelineColorBlendStateCreateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.PipelineColorBlendStateCreateInfo,
|
||||||
|
AttachmentCount = 1,
|
||||||
|
PAttachments = &colorBlendAttachment,
|
||||||
|
};
|
||||||
|
var pipelineInfo = new GraphicsPipelineCreateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.GraphicsPipelineCreateInfo,
|
||||||
|
StageCount = 2,
|
||||||
|
PStages = shaderStages,
|
||||||
|
PVertexInputState = &vertexInput,
|
||||||
|
PInputAssemblyState = &inputAssembly,
|
||||||
|
PViewportState = &viewportState,
|
||||||
|
PRasterizationState = &rasterization,
|
||||||
|
PMultisampleState = &multisample,
|
||||||
|
PColorBlendState = &colorBlend,
|
||||||
|
Layout = _pipelineLayout,
|
||||||
|
RenderPass = _renderPass,
|
||||||
|
Subpass = 0,
|
||||||
|
};
|
||||||
|
Check(
|
||||||
|
_vk.CreateGraphicsPipelines(
|
||||||
|
_device,
|
||||||
|
default,
|
||||||
|
1,
|
||||||
|
&pipelineInfo,
|
||||||
|
null,
|
||||||
|
out _barycentricPipeline),
|
||||||
|
"vkCreateGraphicsPipelines");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
SilkMarshal.Free((nint)entryPoint);
|
||||||
|
_vk.DestroyShaderModule(_device, fragmentModule, null);
|
||||||
|
_vk.DestroyShaderModule(_device, vertexModule, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ShaderModule CreateShaderModule(byte[] code)
|
||||||
|
{
|
||||||
|
fixed (byte* codePointer = code)
|
||||||
|
{
|
||||||
|
var createInfo = new ShaderModuleCreateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.ShaderModuleCreateInfo,
|
||||||
|
CodeSize = (nuint)code.Length,
|
||||||
|
PCode = (uint*)codePointer,
|
||||||
|
};
|
||||||
|
Check(
|
||||||
|
_vk.CreateShaderModule(_device, &createInfo, null, out var module),
|
||||||
|
"vkCreateShaderModule");
|
||||||
|
return module;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void CreateStagingBuffer(ulong size)
|
private void CreateStagingBuffer(ulong size)
|
||||||
{
|
{
|
||||||
var bufferInfo = new BufferCreateInfo
|
var bufferInfo = new BufferCreateInfo
|
||||||
@@ -420,19 +685,34 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
|
|
||||||
private void Render(double _)
|
private void Render(double _)
|
||||||
{
|
{
|
||||||
if (!_vulkanReady || !TryTakeFrame(_presentedSequence, out var frame))
|
if (!_vulkanReady || !TryTakePresentation(_presentedSequence, out var presentation))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var pixels = frame.Width == _extent.Width && frame.Height == _extent.Height
|
if (presentation.Pixels is null &&
|
||||||
? frame.Pixels
|
presentation.DrawKind != GuestDrawKind.FullscreenBarycentric)
|
||||||
: ScaleBgra(frame.Pixels, frame.Width, frame.Height, _extent.Width, _extent.Height);
|
|
||||||
if ((ulong)pixels.Length > _stagingSize)
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
byte[]? pixels = null;
|
||||||
|
if (presentation.Pixels is { } sourcePixels)
|
||||||
|
{
|
||||||
|
pixels = presentation.Width == _extent.Width && presentation.Height == _extent.Height
|
||||||
|
? sourcePixels
|
||||||
|
: ScaleBgra(
|
||||||
|
sourcePixels,
|
||||||
|
presentation.Width,
|
||||||
|
presentation.Height,
|
||||||
|
_extent.Width,
|
||||||
|
_extent.Height);
|
||||||
|
if ((ulong)pixels.Length > _stagingSize)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
uint imageIndex;
|
uint imageIndex;
|
||||||
Check(
|
Check(
|
||||||
_swapchainApi.AcquireNextImage(
|
_swapchainApi.AcquireNextImage(
|
||||||
@@ -444,15 +724,18 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
&imageIndex),
|
&imageIndex),
|
||||||
"vkAcquireNextImageKHR");
|
"vkAcquireNextImageKHR");
|
||||||
|
|
||||||
void* mapped;
|
if (pixels is not null)
|
||||||
Check(
|
|
||||||
_vk.MapMemory(_device, _stagingMemory, 0, (ulong)pixels.Length, 0, &mapped),
|
|
||||||
"vkMapMemory");
|
|
||||||
fixed (byte* source = pixels)
|
|
||||||
{
|
{
|
||||||
System.Buffer.MemoryCopy(source, mapped, pixels.Length, pixels.Length);
|
void* mapped;
|
||||||
|
Check(
|
||||||
|
_vk.MapMemory(_device, _stagingMemory, 0, (ulong)pixels.Length, 0, &mapped),
|
||||||
|
"vkMapMemory");
|
||||||
|
fixed (byte* source = pixels)
|
||||||
|
{
|
||||||
|
System.Buffer.MemoryCopy(source, mapped, pixels.Length, pixels.Length);
|
||||||
|
}
|
||||||
|
_vk.UnmapMemory(_device, _stagingMemory);
|
||||||
}
|
}
|
||||||
_vk.UnmapMemory(_device, _stagingMemory);
|
|
||||||
|
|
||||||
Check(_vk.ResetCommandBuffer(_commandBuffer, 0), "vkResetCommandBuffer");
|
Check(_vk.ResetCommandBuffer(_commandBuffer, 0), "vkResetCommandBuffer");
|
||||||
var beginInfo = new CommandBufferBeginInfo
|
var beginInfo = new CommandBufferBeginInfo
|
||||||
@@ -462,6 +745,93 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
};
|
};
|
||||||
Check(_vk.BeginCommandBuffer(_commandBuffer, &beginInfo), "vkBeginCommandBuffer");
|
Check(_vk.BeginCommandBuffer(_commandBuffer, &beginInfo), "vkBeginCommandBuffer");
|
||||||
|
|
||||||
|
PipelineStageFlags waitStage;
|
||||||
|
if (pixels is not null)
|
||||||
|
{
|
||||||
|
RecordUpload(imageIndex);
|
||||||
|
waitStage = PipelineStageFlags.TransferBit;
|
||||||
|
}
|
||||||
|
else if (presentation.DrawKind == GuestDrawKind.FullscreenBarycentric)
|
||||||
|
{
|
||||||
|
var clearValue = default(ClearValue);
|
||||||
|
var renderPassInfo = new RenderPassBeginInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.RenderPassBeginInfo,
|
||||||
|
RenderPass = _renderPass,
|
||||||
|
Framebuffer = _framebuffers[imageIndex],
|
||||||
|
RenderArea = new Rect2D(new Offset2D(0, 0), _extent),
|
||||||
|
ClearValueCount = 1,
|
||||||
|
PClearValues = &clearValue,
|
||||||
|
};
|
||||||
|
_vk.CmdBeginRenderPass(
|
||||||
|
_commandBuffer,
|
||||||
|
&renderPassInfo,
|
||||||
|
SubpassContents.Inline);
|
||||||
|
_vk.CmdBindPipeline(
|
||||||
|
_commandBuffer,
|
||||||
|
PipelineBindPoint.Graphics,
|
||||||
|
_barycentricPipeline);
|
||||||
|
_vk.CmdDraw(_commandBuffer, 3, 1, 0, 0);
|
||||||
|
_vk.CmdEndRenderPass(_commandBuffer);
|
||||||
|
waitStage = PipelineStageFlags.ColorAttachmentOutputBit;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Unsupported translated guest draw: {presentation.DrawKind}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Check(_vk.EndCommandBuffer(_commandBuffer), "vkEndCommandBuffer");
|
||||||
|
|
||||||
|
var imageAvailable = _imageAvailable;
|
||||||
|
var commandBuffer = _commandBuffer;
|
||||||
|
var renderFinished = _renderFinished;
|
||||||
|
var submitInfo = new SubmitInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.SubmitInfo,
|
||||||
|
WaitSemaphoreCount = 1,
|
||||||
|
PWaitSemaphores = &imageAvailable,
|
||||||
|
PWaitDstStageMask = &waitStage,
|
||||||
|
CommandBufferCount = 1,
|
||||||
|
PCommandBuffers = &commandBuffer,
|
||||||
|
SignalSemaphoreCount = 1,
|
||||||
|
PSignalSemaphores = &renderFinished,
|
||||||
|
};
|
||||||
|
Check(_vk.QueueSubmit(_queue, 1, &submitInfo, default), "vkQueueSubmit");
|
||||||
|
|
||||||
|
var swapchain = _swapchain;
|
||||||
|
var presentInfo = new PresentInfoKHR
|
||||||
|
{
|
||||||
|
SType = StructureType.PresentInfoKhr,
|
||||||
|
WaitSemaphoreCount = 1,
|
||||||
|
PWaitSemaphores = &renderFinished,
|
||||||
|
SwapchainCount = 1,
|
||||||
|
PSwapchains = &swapchain,
|
||||||
|
PImageIndices = &imageIndex,
|
||||||
|
};
|
||||||
|
Check(_swapchainApi.QueuePresent(_queue, &presentInfo), "vkQueuePresentKHR");
|
||||||
|
Check(_vk.QueueWaitIdle(_queue), "vkQueueWaitIdle");
|
||||||
|
_imageInitialized[imageIndex] = true;
|
||||||
|
_presentedSequence = presentation.Sequence;
|
||||||
|
if (!_firstFramePresented)
|
||||||
|
{
|
||||||
|
_firstFramePresented = true;
|
||||||
|
Console.Error.WriteLine(
|
||||||
|
$"[LOADER][INFO] Vulkan VideoOut presented first frame: " +
|
||||||
|
$"{presentation.Width}x{presentation.Height}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pixels is null && !_firstGuestDrawPresented)
|
||||||
|
{
|
||||||
|
_firstGuestDrawPresented = true;
|
||||||
|
Console.Error.WriteLine(
|
||||||
|
$"[LOADER][INFO] Vulkan VideoOut presented translated guest draw: " +
|
||||||
|
$"{presentation.DrawKind}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RecordUpload(uint imageIndex)
|
||||||
|
{
|
||||||
var oldLayout = _imageInitialized[imageIndex]
|
var oldLayout = _imageInitialized[imageIndex]
|
||||||
? ImageLayout.PresentSrcKhr
|
? ImageLayout.PresentSrcKhr
|
||||||
: ImageLayout.Undefined;
|
: ImageLayout.Undefined;
|
||||||
@@ -479,7 +849,9 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
};
|
};
|
||||||
_vk.CmdPipelineBarrier(
|
_vk.CmdPipelineBarrier(
|
||||||
_commandBuffer,
|
_commandBuffer,
|
||||||
_imageInitialized[imageIndex] ? PipelineStageFlags.BottomOfPipeBit : PipelineStageFlags.TopOfPipeBit,
|
_imageInitialized[imageIndex]
|
||||||
|
? PipelineStageFlags.BottomOfPipeBit
|
||||||
|
: PipelineStageFlags.TopOfPipeBit,
|
||||||
PipelineStageFlags.TransferBit,
|
PipelineStageFlags.TransferBit,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
@@ -529,45 +901,6 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
null,
|
null,
|
||||||
1,
|
1,
|
||||||
&toPresent);
|
&toPresent);
|
||||||
Check(_vk.EndCommandBuffer(_commandBuffer), "vkEndCommandBuffer");
|
|
||||||
|
|
||||||
var waitStage = PipelineStageFlags.TransferBit;
|
|
||||||
var imageAvailable = _imageAvailable;
|
|
||||||
var commandBuffer = _commandBuffer;
|
|
||||||
var renderFinished = _renderFinished;
|
|
||||||
var submitInfo = new SubmitInfo
|
|
||||||
{
|
|
||||||
SType = StructureType.SubmitInfo,
|
|
||||||
WaitSemaphoreCount = 1,
|
|
||||||
PWaitSemaphores = &imageAvailable,
|
|
||||||
PWaitDstStageMask = &waitStage,
|
|
||||||
CommandBufferCount = 1,
|
|
||||||
PCommandBuffers = &commandBuffer,
|
|
||||||
SignalSemaphoreCount = 1,
|
|
||||||
PSignalSemaphores = &renderFinished,
|
|
||||||
};
|
|
||||||
Check(_vk.QueueSubmit(_queue, 1, &submitInfo, default), "vkQueueSubmit");
|
|
||||||
|
|
||||||
var swapchain = _swapchain;
|
|
||||||
var presentInfo = new PresentInfoKHR
|
|
||||||
{
|
|
||||||
SType = StructureType.PresentInfoKhr,
|
|
||||||
WaitSemaphoreCount = 1,
|
|
||||||
PWaitSemaphores = &renderFinished,
|
|
||||||
SwapchainCount = 1,
|
|
||||||
PSwapchains = &swapchain,
|
|
||||||
PImageIndices = &imageIndex,
|
|
||||||
};
|
|
||||||
Check(_swapchainApi.QueuePresent(_queue, &presentInfo), "vkQueuePresentKHR");
|
|
||||||
Check(_vk.QueueWaitIdle(_queue), "vkQueueWaitIdle");
|
|
||||||
_imageInitialized[imageIndex] = true;
|
|
||||||
_presentedSequence = frame.Sequence;
|
|
||||||
if (!_firstFramePresented)
|
|
||||||
{
|
|
||||||
_firstFramePresented = true;
|
|
||||||
Console.Error.WriteLine(
|
|
||||||
$"[LOADER][INFO] Vulkan VideoOut presented first guest frame: {frame.Width}x{frame.Height}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Extent2D ChooseExtent(SurfaceCapabilitiesKHR capabilities)
|
private Extent2D ChooseExtent(SurfaceCapabilitiesKHR capabilities)
|
||||||
@@ -669,6 +1002,32 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
{
|
{
|
||||||
_vk.DestroySemaphore(_device, _renderFinished, null);
|
_vk.DestroySemaphore(_device, _renderFinished, null);
|
||||||
}
|
}
|
||||||
|
if (_barycentricPipeline.Handle != 0)
|
||||||
|
{
|
||||||
|
_vk.DestroyPipeline(_device, _barycentricPipeline, null);
|
||||||
|
}
|
||||||
|
if (_pipelineLayout.Handle != 0)
|
||||||
|
{
|
||||||
|
_vk.DestroyPipelineLayout(_device, _pipelineLayout, null);
|
||||||
|
}
|
||||||
|
foreach (var framebuffer in _framebuffers)
|
||||||
|
{
|
||||||
|
if (framebuffer.Handle != 0)
|
||||||
|
{
|
||||||
|
_vk.DestroyFramebuffer(_device, framebuffer, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_renderPass.Handle != 0)
|
||||||
|
{
|
||||||
|
_vk.DestroyRenderPass(_device, _renderPass, null);
|
||||||
|
}
|
||||||
|
foreach (var imageView in _swapchainImageViews)
|
||||||
|
{
|
||||||
|
if (imageView.Handle != 0)
|
||||||
|
{
|
||||||
|
_vk.DestroyImageView(_device, imageView, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (_commandPool.Handle != 0)
|
if (_commandPool.Handle != 0)
|
||||||
{
|
{
|
||||||
_vk.DestroyCommandPool(_device, _commandPool, null);
|
_vk.DestroyCommandPool(_device, _commandPool, null);
|
||||||
|
|||||||
Reference in New Issue
Block a user