mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-25 20:28:48 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 69d8490275 |
@@ -3609,7 +3609,7 @@ public static class AgcExports
|
|||||||
',',
|
',',
|
||||||
draw.GlobalMemoryBindings.Select((binding, index) =>
|
draw.GlobalMemoryBindings.Select((binding, index) =>
|
||||||
$"{index}:0x{binding.BaseAddress:X16}:{binding.Data.Length}:" +
|
$"{index}:0x{binding.BaseAddress:X16}:{binding.Data.Length}:" +
|
||||||
Convert.ToHexString(binding.Data.AsSpan(0, Math.Min(binding.Data.Length, 32)))));
|
Convert.ToHexString(binding.Data.AsSpan(0, Math.Min(binding.Data.Length, 256)))));
|
||||||
var indices = draw.IndexBuffer is { } indexBuffer
|
var indices = draw.IndexBuffer is { } indexBuffer
|
||||||
? $"{(indexBuffer.Is32Bit ? 32 : 16)}:" +
|
? $"{(indexBuffer.Is32Bit ? 32 : 16)}:" +
|
||||||
Convert.ToHexString(indexBuffer.Data.AsSpan(0, Math.Min(indexBuffer.Data.Length, 32)))
|
Convert.ToHexString(indexBuffer.Data.AsSpan(0, Math.Min(indexBuffer.Data.Length, 32)))
|
||||||
@@ -3744,7 +3744,7 @@ public static class AgcExports
|
|||||||
descriptor.Width > 8192 ||
|
descriptor.Width > 8192 ||
|
||||||
descriptor.Height > 8192)
|
descriptor.Height > 8192)
|
||||||
{
|
{
|
||||||
texture = CreateFallbackGuestDrawTexture(isStorage);
|
texture = CreateFallbackGuestDrawTexture(isStorage, descriptor.Format, descriptor.NumberType);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3762,7 +3762,7 @@ public static class AgcExports
|
|||||||
sourceByteCount > MaxPresentedTextureBytes ||
|
sourceByteCount > MaxPresentedTextureBytes ||
|
||||||
sourceByteCount > int.MaxValue)
|
sourceByteCount > int.MaxValue)
|
||||||
{
|
{
|
||||||
texture = CreateFallbackGuestDrawTexture(isStorage);
|
texture = CreateFallbackGuestDrawTexture(isStorage, descriptor.Format, descriptor.NumberType);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3825,7 +3825,7 @@ public static class AgcExports
|
|||||||
var source = new byte[(int)sourceByteCount];
|
var source = new byte[(int)sourceByteCount];
|
||||||
if (!ctx.Memory.TryRead(descriptor.Address, source))
|
if (!ctx.Memory.TryRead(descriptor.Address, source))
|
||||||
{
|
{
|
||||||
texture = CreateFallbackGuestDrawTexture(isStorage);
|
texture = CreateFallbackGuestDrawTexture(isStorage, descriptor.Format, descriptor.NumberType);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3868,18 +3868,25 @@ public static class AgcExports
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static VulkanGuestDrawTexture CreateFallbackGuestDrawTexture(bool isStorage = false) =>
|
private static VulkanGuestDrawTexture CreateFallbackGuestDrawTexture(
|
||||||
new(
|
bool isStorage,
|
||||||
|
uint format,
|
||||||
|
uint numberType)
|
||||||
|
{
|
||||||
|
var fallbackFormat = format == 0 ? 10u : format;
|
||||||
|
var fallbackNumberType = numberType;
|
||||||
|
return new(
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
56,
|
fallbackFormat,
|
||||||
NumberType: 0,
|
fallbackNumberType,
|
||||||
[0, 0, 0, 255],
|
[0, 0, 0, 255],
|
||||||
IsFallback: true,
|
IsFallback: true,
|
||||||
IsStorage: isStorage,
|
IsStorage: isStorage,
|
||||||
MipLevels: 1,
|
MipLevels: 1,
|
||||||
MipLevel: 0);
|
MipLevel: 0);
|
||||||
|
}
|
||||||
|
|
||||||
private static VulkanGuestSampler ToVulkanSampler(IReadOnlyList<uint> descriptor) =>
|
private static VulkanGuestSampler ToVulkanSampler(IReadOnlyList<uint> descriptor) =>
|
||||||
descriptor.Count >= 4
|
descriptor.Count >= 4
|
||||||
|
|||||||
@@ -467,9 +467,6 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
_imageBindingByPc.TryAdd(binding.Pc, index);
|
_imageBindingByPc.TryAdd(binding.Pc, index);
|
||||||
var isStorage =
|
var isStorage =
|
||||||
Gen5ShaderTranslator.IsStorageImageOperation(binding.Opcode);
|
Gen5ShaderTranslator.IsStorageImageOperation(binding.Opcode);
|
||||||
var isDepth =
|
|
||||||
binding.Opcode.Contains("SampleC", StringComparison.Ordinal) ||
|
|
||||||
binding.Opcode.Contains("Gather4C", StringComparison.Ordinal);
|
|
||||||
var (format, componentKind) =
|
var (format, componentKind) =
|
||||||
DecodeImageFormat(binding.ResourceDescriptor);
|
DecodeImageFormat(binding.ResourceDescriptor);
|
||||||
var componentType = componentKind switch
|
var componentType = componentKind switch
|
||||||
@@ -494,7 +491,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
var imageType = _module.TypeImage(
|
var imageType = _module.TypeImage(
|
||||||
componentType,
|
componentType,
|
||||||
SpirvImageDim.Dim2D,
|
SpirvImageDim.Dim2D,
|
||||||
depth: isDepth,
|
depth: false,
|
||||||
arrayed: false,
|
arrayed: false,
|
||||||
multisampled: false,
|
multisampled: false,
|
||||||
sampled: isStorage ? 2u : 1u,
|
sampled: isStorage ? 2u : 1u,
|
||||||
@@ -1661,18 +1658,14 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
var offset = hasOffset ? BuildImageOffset(image, 0) : 0u;
|
var offset = hasOffset ? BuildImageOffset(image, 0) : 0u;
|
||||||
var imageOperands =
|
var imageOperands =
|
||||||
(explicitLod ? 2u : 0u) | (hasOffset ? 0x10u : 0u);
|
(explicitLod ? 2u : 0u) | (hasOffset ? 0x10u : 0u);
|
||||||
|
var reference = hasCompare
|
||||||
|
? Bitcast(_floatType, LoadV(image.GetAddressRegister(hasOffset ? 1 : 0)))
|
||||||
|
: 0u;
|
||||||
var operands = new List<uint>
|
var operands = new List<uint>
|
||||||
{
|
{
|
||||||
imageObject,
|
imageObject,
|
||||||
coordinates,
|
coordinates,
|
||||||
};
|
};
|
||||||
if (hasCompare)
|
|
||||||
{
|
|
||||||
operands.Add(
|
|
||||||
Bitcast(
|
|
||||||
_floatType,
|
|
||||||
LoadV(image.GetAddressRegister(hasOffset ? 1 : 0))));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (imageOperands != 0)
|
if (imageOperands != 0)
|
||||||
{
|
{
|
||||||
@@ -1689,29 +1682,14 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
}
|
}
|
||||||
|
|
||||||
sampled = _module.AddInstruction(
|
sampled = _module.AddInstruction(
|
||||||
hasCompare
|
explicitLod
|
||||||
? explicitLod
|
? SpirvOp.ImageSampleExplicitLod
|
||||||
? SpirvOp.ImageSampleDrefExplicitLod
|
: SpirvOp.ImageSampleImplicitLod,
|
||||||
: SpirvOp.ImageSampleDrefImplicitLod
|
resource.VectorType,
|
||||||
: explicitLod
|
|
||||||
? SpirvOp.ImageSampleExplicitLod
|
|
||||||
: SpirvOp.ImageSampleImplicitLod,
|
|
||||||
hasCompare ? resource.ComponentType : resource.VectorType,
|
|
||||||
[.. operands]);
|
[.. operands]);
|
||||||
if (hasCompare)
|
if (hasCompare)
|
||||||
{
|
{
|
||||||
var scalar = sampled;
|
sampled = EmitManualDepthCompare(resource, sampled, reference);
|
||||||
sampled = _module.AddInstruction(
|
|
||||||
SpirvOp.CompositeConstruct,
|
|
||||||
resource.VectorType,
|
|
||||||
scalar,
|
|
||||||
scalar,
|
|
||||||
scalar,
|
|
||||||
resource.ComponentKind == ImageComponentKind.Float
|
|
||||||
? Float(1)
|
|
||||||
: resource.ComponentKind == ImageComponentKind.Uint
|
|
||||||
? UInt(1)
|
|
||||||
: _module.Constant(_intType, 1));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (instruction.Opcode.StartsWith(
|
else if (instruction.Opcode.StartsWith(
|
||||||
@@ -1725,6 +1703,9 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
var start = (hasOffset ? 1 : 0) + (hasCompare ? 1 : 0);
|
var start = (hasOffset ? 1 : 0) + (hasCompare ? 1 : 0);
|
||||||
var coordinates = BuildFloatCoordinates(image, start);
|
var coordinates = BuildFloatCoordinates(image, start);
|
||||||
var offset = hasOffset ? BuildImageOffset(image, 0) : 0u;
|
var offset = hasOffset ? BuildImageOffset(image, 0) : 0u;
|
||||||
|
var reference = hasCompare
|
||||||
|
? Bitcast(_floatType, LoadV(image.GetAddressRegister(hasOffset ? 1 : 0)))
|
||||||
|
: 0u;
|
||||||
var operands = new List<uint>
|
var operands = new List<uint>
|
||||||
{
|
{
|
||||||
imageObject,
|
imageObject,
|
||||||
@@ -1732,10 +1713,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
};
|
};
|
||||||
if (hasCompare)
|
if (hasCompare)
|
||||||
{
|
{
|
||||||
operands.Add(
|
operands.Add(UInt(0));
|
||||||
Bitcast(
|
|
||||||
_floatType,
|
|
||||||
LoadV(image.GetAddressRegister(hasOffset ? 1 : 0))));
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1756,11 +1734,28 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
}
|
}
|
||||||
|
|
||||||
sampled = _module.AddInstruction(
|
sampled = _module.AddInstruction(
|
||||||
hasCompare
|
SpirvOp.ImageGather,
|
||||||
? SpirvOp.ImageDrefGather
|
|
||||||
: SpirvOp.ImageGather,
|
|
||||||
resource.VectorType,
|
resource.VectorType,
|
||||||
[.. operands]);
|
[.. operands]);
|
||||||
|
if (hasCompare)
|
||||||
|
{
|
||||||
|
var compared = new uint[4];
|
||||||
|
for (var component = 0u; component < 4; component++)
|
||||||
|
{
|
||||||
|
var texel = _module.AddInstruction(
|
||||||
|
SpirvOp.CompositeExtract,
|
||||||
|
resource.ComponentType,
|
||||||
|
sampled,
|
||||||
|
component);
|
||||||
|
compared[component] = EmitDepthCompareScalar(resource, texel, reference);
|
||||||
|
}
|
||||||
|
|
||||||
|
sampled = _module.AddInstruction(
|
||||||
|
SpirvOp.CompositeConstruct,
|
||||||
|
resource.VectorType,
|
||||||
|
compared);
|
||||||
|
}
|
||||||
|
|
||||||
writeAllComponents = true;
|
writeAllComponents = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -1794,6 +1789,67 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private uint EmitDepthCompareScalar(
|
||||||
|
SpirvImageResource resource,
|
||||||
|
uint texel,
|
||||||
|
uint reference)
|
||||||
|
{
|
||||||
|
var texelAsFloat = resource.ComponentKind switch
|
||||||
|
{
|
||||||
|
ImageComponentKind.Uint => _module.AddInstruction(
|
||||||
|
SpirvOp.ConvertUToF, _floatType, texel),
|
||||||
|
ImageComponentKind.Sint => _module.AddInstruction(
|
||||||
|
SpirvOp.ConvertSToF, _floatType, texel),
|
||||||
|
_ => texel,
|
||||||
|
};
|
||||||
|
var passes = _module.AddInstruction(
|
||||||
|
SpirvOp.FOrdLessThanEqual,
|
||||||
|
_boolType,
|
||||||
|
reference,
|
||||||
|
texelAsFloat);
|
||||||
|
return _module.AddInstruction(
|
||||||
|
SpirvOp.Select,
|
||||||
|
resource.ComponentType,
|
||||||
|
passes,
|
||||||
|
resource.ComponentKind switch
|
||||||
|
{
|
||||||
|
ImageComponentKind.Uint => UInt(1),
|
||||||
|
ImageComponentKind.Sint => _module.Constant(_intType, 1),
|
||||||
|
_ => Float(1),
|
||||||
|
},
|
||||||
|
resource.ComponentKind switch
|
||||||
|
{
|
||||||
|
ImageComponentKind.Uint => UInt(0),
|
||||||
|
ImageComponentKind.Sint => _module.Constant(_intType, 0),
|
||||||
|
_ => Float(0),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint EmitManualDepthCompare(
|
||||||
|
SpirvImageResource resource,
|
||||||
|
uint sampledVector,
|
||||||
|
uint reference)
|
||||||
|
{
|
||||||
|
var texel = _module.AddInstruction(
|
||||||
|
SpirvOp.CompositeExtract,
|
||||||
|
resource.ComponentType,
|
||||||
|
sampledVector,
|
||||||
|
0u);
|
||||||
|
var scalar = EmitDepthCompareScalar(resource, texel, reference);
|
||||||
|
return _module.AddInstruction(
|
||||||
|
SpirvOp.CompositeConstruct,
|
||||||
|
resource.VectorType,
|
||||||
|
scalar,
|
||||||
|
scalar,
|
||||||
|
scalar,
|
||||||
|
resource.ComponentKind switch
|
||||||
|
{
|
||||||
|
ImageComponentKind.Uint => UInt(1),
|
||||||
|
ImageComponentKind.Sint => _module.Constant(_intType, 1),
|
||||||
|
_ => Float(1),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private uint BuildFloatCoordinates(Gen5ImageControl image, int start)
|
private uint BuildFloatCoordinates(Gen5ImageControl image, int start)
|
||||||
{
|
{
|
||||||
var x = Bitcast(
|
var x = Bitcast(
|
||||||
|
|||||||
@@ -1459,8 +1459,20 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
VertexPipelineStoresAndAtomics = supportedFeatures.VertexPipelineStoresAndAtomics,
|
VertexPipelineStoresAndAtomics = supportedFeatures.VertexPipelineStoresAndAtomics,
|
||||||
FragmentStoresAndAtomics = supportedFeatures.FragmentStoresAndAtomics,
|
FragmentStoresAndAtomics = supportedFeatures.FragmentStoresAndAtomics,
|
||||||
ShaderInt64 = supportedFeatures.ShaderInt64,
|
ShaderInt64 = supportedFeatures.ShaderInt64,
|
||||||
|
ShaderImageGatherExtended = supportedFeatures.ShaderImageGatherExtended,
|
||||||
|
ShaderStorageImageExtendedFormats = supportedFeatures.ShaderStorageImageExtendedFormats,
|
||||||
|
ShaderStorageImageReadWithoutFormat = supportedFeatures.ShaderStorageImageReadWithoutFormat,
|
||||||
|
ShaderStorageImageWriteWithoutFormat = supportedFeatures.ShaderStorageImageWriteWithoutFormat,
|
||||||
|
RobustBufferAccess = supportedFeatures.RobustBufferAccess,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!supportedFeatures.RobustBufferAccess)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine(
|
||||||
|
"[LOADER][WARN] GPU does not support robustBufferAccess " +
|
||||||
|
"translated shaders performing out-of-bounds buffer access may cause device loss.");
|
||||||
|
}
|
||||||
|
|
||||||
if (!supportedFeatures.ShaderInt64)
|
if (!supportedFeatures.ShaderInt64)
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine(
|
Console.Error.WriteLine(
|
||||||
@@ -1474,18 +1486,96 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
"[LOADER][WARN] GPU does not support vertexPipelineStoresAndAtomics/fragmentStoresAndAtomics " +
|
"[LOADER][WARN] GPU does not support vertexPipelineStoresAndAtomics/fragmentStoresAndAtomics " +
|
||||||
"translated shaders using storage buffers in vertex/fragment stages may fail.");
|
"translated shaders using storage buffers in vertex/fragment stages may fail.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!supportedFeatures.ShaderImageGatherExtended)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine(
|
||||||
|
"[LOADER][WARN] GPU does not support shaderImageGatherExtended " +
|
||||||
|
"translated shaders using image gather with offsets/LOD/bias will fail.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!supportedFeatures.ShaderStorageImageReadWithoutFormat ||
|
||||||
|
!supportedFeatures.ShaderStorageImageWriteWithoutFormat)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine(
|
||||||
|
"[LOADER][WARN] GPU does not support shaderStorageImage(Read|Write)WithoutFormat " +
|
||||||
|
"translated shaders using unformatted storage image load/store will fail.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var maintenance8Features = new PhysicalDeviceMaintenance8FeaturesKHR
|
||||||
|
{
|
||||||
|
SType = StructureType.PhysicalDeviceMaintenance8FeaturesKhr,
|
||||||
|
};
|
||||||
|
var robustness2Features = new PhysicalDeviceRobustness2FeaturesEXT
|
||||||
|
{
|
||||||
|
SType = StructureType.PhysicalDeviceRobustness2FeaturesExt,
|
||||||
|
PNext = &maintenance8Features,
|
||||||
|
};
|
||||||
|
var featuresQuery = new PhysicalDeviceFeatures2
|
||||||
|
{
|
||||||
|
SType = StructureType.PhysicalDeviceFeatures2,
|
||||||
|
PNext = &robustness2Features,
|
||||||
|
};
|
||||||
|
_vk.GetPhysicalDeviceFeatures2(_physicalDevice, &featuresQuery);
|
||||||
|
var supportsMaintenance8 = maintenance8Features.Maintenance8;
|
||||||
|
var supportsRobustImageAccess2 = robustness2Features.RobustImageAccess2;
|
||||||
|
var supportsNullDescriptor = robustness2Features.NullDescriptor;
|
||||||
|
var supportsRobustness2 = supportsRobustImageAccess2 || supportsNullDescriptor;
|
||||||
|
if (!supportsMaintenance8)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine(
|
||||||
|
"[LOADER][WARN] GPU does not support VK_KHR_maintenance8 " +
|
||||||
|
"translated shaders using a dynamic texel offset on non-gather image samples will fail.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!supportsRobustImageAccess2)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine(
|
||||||
|
"[LOADER][WARN] GPU does not support VK_EXT_robustness2 robustImageAccess2 " +
|
||||||
|
"translated shaders performing out-of-bounds image access may cause device loss.");
|
||||||
|
}
|
||||||
|
|
||||||
var swapchainExtension = (byte*)SilkMarshal.StringToPtr("VK_KHR_swapchain");
|
var swapchainExtension = (byte*)SilkMarshal.StringToPtr("VK_KHR_swapchain");
|
||||||
|
var maintenance8Extension = (byte*)SilkMarshal.StringToPtr("VK_KHR_maintenance8");
|
||||||
|
var robustness2Extension = (byte*)SilkMarshal.StringToPtr("VK_EXT_robustness2");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
var extensions = stackalloc byte*[3];
|
||||||
|
var extensionCount = 0u;
|
||||||
|
extensions[extensionCount++] = swapchainExtension;
|
||||||
|
if (supportsMaintenance8)
|
||||||
|
{
|
||||||
|
extensions[extensionCount++] = maintenance8Extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (supportsRobustness2)
|
||||||
|
{
|
||||||
|
extensions[extensionCount++] = robustness2Extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
maintenance8Features.Maintenance8 = supportsMaintenance8;
|
||||||
|
maintenance8Features.PNext = null;
|
||||||
|
robustness2Features.RobustBufferAccess2 =
|
||||||
|
supportsRobustImageAccess2 && supportedFeatures.RobustBufferAccess;
|
||||||
|
robustness2Features.RobustImageAccess2 = supportsRobustImageAccess2;
|
||||||
|
robustness2Features.NullDescriptor = supportsNullDescriptor;
|
||||||
|
robustness2Features.PNext = supportsMaintenance8 ? &maintenance8Features : null;
|
||||||
|
var features2 = new PhysicalDeviceFeatures2
|
||||||
|
{
|
||||||
|
SType = StructureType.PhysicalDeviceFeatures2,
|
||||||
|
PNext = supportsRobustness2
|
||||||
|
? &robustness2Features
|
||||||
|
: (supportsMaintenance8 ? &maintenance8Features : null),
|
||||||
|
Features = enabledFeatures,
|
||||||
|
};
|
||||||
var createInfo = new DeviceCreateInfo
|
var createInfo = new DeviceCreateInfo
|
||||||
{
|
{
|
||||||
SType = StructureType.DeviceCreateInfo,
|
SType = StructureType.DeviceCreateInfo,
|
||||||
|
PNext = &features2,
|
||||||
QueueCreateInfoCount = 1,
|
QueueCreateInfoCount = 1,
|
||||||
PQueueCreateInfos = &queueInfo,
|
PQueueCreateInfos = &queueInfo,
|
||||||
EnabledExtensionCount = 1,
|
EnabledExtensionCount = extensionCount,
|
||||||
PpEnabledExtensionNames = &swapchainExtension,
|
PpEnabledExtensionNames = extensions,
|
||||||
PEnabledFeatures = &enabledFeatures,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Check(_vk.CreateDevice(_physicalDevice, &createInfo, null, out _device), "vkCreateDevice");
|
Check(_vk.CreateDevice(_physicalDevice, &createInfo, null, out _device), "vkCreateDevice");
|
||||||
@@ -1493,6 +1583,8 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
SilkMarshal.Free((nint)swapchainExtension);
|
SilkMarshal.Free((nint)swapchainExtension);
|
||||||
|
SilkMarshal.Free((nint)maintenance8Extension);
|
||||||
|
SilkMarshal.Free((nint)robustness2Extension);
|
||||||
}
|
}
|
||||||
|
|
||||||
_vk.GetDeviceQueue(_device, _queueFamilyIndex, 0, out _queue);
|
_vk.GetDeviceQueue(_device, _queueFamilyIndex, 0, out _queue);
|
||||||
@@ -1657,6 +1749,39 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
resources.DebugName));
|
resources.DebugName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SubmitGuestCommandBufferAndWait(CommandBuffer commandBuffer)
|
||||||
|
{
|
||||||
|
var fenceInfo = new FenceCreateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.FenceCreateInfo,
|
||||||
|
};
|
||||||
|
Fence fence;
|
||||||
|
Check(
|
||||||
|
_vk.CreateFence(_device, &fenceInfo, null, out fence),
|
||||||
|
"vkCreateFence(guest chunk)");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var submitInfo = new SubmitInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.SubmitInfo,
|
||||||
|
CommandBufferCount = 1,
|
||||||
|
PCommandBuffers = &commandBuffer,
|
||||||
|
};
|
||||||
|
Check(
|
||||||
|
_vk.QueueSubmit(_queue, 1, &submitInfo, fence),
|
||||||
|
"vkQueueSubmit(guest chunk)");
|
||||||
|
Check(
|
||||||
|
_vk.WaitForFences(_device, 1, &fence, true, ulong.MaxValue),
|
||||||
|
"vkWaitForFences(guest chunk)");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_vk.DestroyFence(_device, fence, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
_vk.FreeCommandBuffers(_device, _commandPool, 1, &commandBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
private void EnsureGuestSubmissionCapacity()
|
private void EnsureGuestSubmissionCapacity()
|
||||||
{
|
{
|
||||||
CollectCompletedGuestSubmissions(waitForOldest: false);
|
CollectCompletedGuestSubmissions(waitForOldest: false);
|
||||||
@@ -2641,6 +2766,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
var pipelineInfo = new ComputePipelineCreateInfo
|
var pipelineInfo = new ComputePipelineCreateInfo
|
||||||
{
|
{
|
||||||
SType = StructureType.ComputePipelineCreateInfo,
|
SType = StructureType.ComputePipelineCreateInfo,
|
||||||
|
Flags = PipelineCreateFlags.CreateDispatchBaseBit,
|
||||||
Stage = stage,
|
Stage = stage,
|
||||||
Layout = resources.PipelineLayout,
|
Layout = resources.PipelineLayout,
|
||||||
};
|
};
|
||||||
@@ -2986,9 +3112,13 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
}
|
}
|
||||||
_vk.UnmapMemory(_device, stagingMemory);
|
_vk.UnmapMemory(_device, stagingMemory);
|
||||||
|
|
||||||
|
var supportsAttachmentUsage = !IsBlockCompressedFormat(vkFormat);
|
||||||
var imageInfo = new ImageCreateInfo
|
var imageInfo = new ImageCreateInfo
|
||||||
{
|
{
|
||||||
SType = StructureType.ImageCreateInfo,
|
SType = StructureType.ImageCreateInfo,
|
||||||
|
Flags = supportsAttachmentUsage
|
||||||
|
? ImageCreateFlags.CreateMutableFormatBit | ImageCreateFlags.CreateExtendedUsageBit
|
||||||
|
: 0,
|
||||||
ImageType = ImageType.Type2D,
|
ImageType = ImageType.Type2D,
|
||||||
Format = vkFormat,
|
Format = vkFormat,
|
||||||
Extent = new Extent3D(width, height, 1),
|
Extent = new Extent3D(width, height, 1),
|
||||||
@@ -2996,7 +3126,13 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
ArrayLayers = 1,
|
ArrayLayers = 1,
|
||||||
Samples = SampleCountFlags.Count1Bit,
|
Samples = SampleCountFlags.Count1Bit,
|
||||||
Tiling = ImageTiling.Optimal,
|
Tiling = ImageTiling.Optimal,
|
||||||
Usage = ImageUsageFlags.TransferDstBit | ImageUsageFlags.SampledBit,
|
Usage = supportsAttachmentUsage
|
||||||
|
? ImageUsageFlags.TransferDstBit |
|
||||||
|
ImageUsageFlags.SampledBit |
|
||||||
|
ImageUsageFlags.ColorAttachmentBit |
|
||||||
|
ImageUsageFlags.StorageBit |
|
||||||
|
ImageUsageFlags.TransferSrcBit
|
||||||
|
: ImageUsageFlags.TransferDstBit | ImageUsageFlags.SampledBit,
|
||||||
SharingMode = SharingMode.Exclusive,
|
SharingMode = SharingMode.Exclusive,
|
||||||
InitialLayout = ImageLayout.Undefined,
|
InitialLayout = ImageLayout.Undefined,
|
||||||
};
|
};
|
||||||
@@ -3831,6 +3967,12 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
_ => Format.Undefined,
|
_ => Format.Undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static bool IsBlockCompressedFormat(Format format) =>
|
||||||
|
format is Format.BC1RgbaUnormBlock or
|
||||||
|
Format.BC1RgbaSrgbBlock or
|
||||||
|
Format.BC7UnormBlock or
|
||||||
|
Format.BC7SrgbBlock;
|
||||||
|
|
||||||
private VkBuffer CreateBuffer(
|
private VkBuffer CreateBuffer(
|
||||||
ulong size,
|
ulong size,
|
||||||
BufferUsageFlags usage,
|
BufferUsageFlags usage,
|
||||||
@@ -3884,8 +4026,19 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
throw new InvalidOperationException("No compatible Vulkan host-visible memory type was found.");
|
throw new InvalidOperationException("No compatible Vulkan host-visible memory type was found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const uint MaxComputeZSlicesPerSubmission = 8;
|
||||||
|
|
||||||
private void ExecuteComputeDispatch(VulkanComputeGuestDispatch work)
|
private void ExecuteComputeDispatch(VulkanComputeGuestDispatch work)
|
||||||
{
|
{
|
||||||
|
if (AddressListContains("SHARPEMU_SKIP_COMPUTE_CS", work.ShaderAddress))
|
||||||
|
{
|
||||||
|
TraceVulkanShader(
|
||||||
|
$"vk.compute_skip cs=0x{work.ShaderAddress:X16} " +
|
||||||
|
$"groups={work.GroupCountX}x{work.GroupCountY}x{work.GroupCountZ} " +
|
||||||
|
$"textures={work.Textures.Count}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
TranslatedDrawResources? resources = null;
|
TranslatedDrawResources? resources = null;
|
||||||
CommandBuffer commandBuffer = default;
|
CommandBuffer commandBuffer = default;
|
||||||
var submitted = false;
|
var submitted = false;
|
||||||
@@ -3893,59 +4046,95 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
{
|
{
|
||||||
EnsureGuestSubmissionCapacity();
|
EnsureGuestSubmissionCapacity();
|
||||||
resources = CreateComputeDispatchResources(work);
|
resources = CreateComputeDispatchResources(work);
|
||||||
commandBuffer = AllocateGuestCommandBuffer();
|
|
||||||
_commandBuffer = commandBuffer;
|
|
||||||
var beginInfo = new CommandBufferBeginInfo
|
|
||||||
{
|
|
||||||
SType = StructureType.CommandBufferBeginInfo,
|
|
||||||
Flags = CommandBufferUsageFlags.OneTimeSubmitBit,
|
|
||||||
};
|
|
||||||
Check(
|
|
||||||
_vk.BeginCommandBuffer(_commandBuffer, &beginInfo),
|
|
||||||
"vkBeginCommandBuffer(compute)");
|
|
||||||
|
|
||||||
BeginDebugLabel(_commandBuffer, resources.DebugName);
|
var batchCount = Math.Max(
|
||||||
RecordTextureUploads(resources, PipelineStageFlags.ComputeShaderBit);
|
1u,
|
||||||
RecordStorageImagesForWrite(resources, PipelineStageFlags.ComputeShaderBit);
|
(uint)Math.Ceiling(work.GroupCountZ / (double)MaxComputeZSlicesPerSubmission));
|
||||||
_vk.CmdBindPipeline(
|
|
||||||
_commandBuffer,
|
for (var batchIndex = 0u; batchIndex < batchCount; batchIndex++)
|
||||||
PipelineBindPoint.Compute,
|
|
||||||
resources.Pipeline);
|
|
||||||
if (resources.DescriptorSet.Handle != 0)
|
|
||||||
{
|
{
|
||||||
var descriptorSet = resources.DescriptorSet;
|
var zStart = batchIndex * MaxComputeZSlicesPerSubmission;
|
||||||
_vk.CmdBindDescriptorSets(
|
var zCount = Math.Min(MaxComputeZSlicesPerSubmission, work.GroupCountZ - zStart);
|
||||||
|
var isFirstBatch = batchIndex == 0;
|
||||||
|
var isLastBatch = batchIndex == batchCount - 1;
|
||||||
|
|
||||||
|
commandBuffer = AllocateGuestCommandBuffer();
|
||||||
|
_commandBuffer = commandBuffer;
|
||||||
|
var beginInfo = new CommandBufferBeginInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.CommandBufferBeginInfo,
|
||||||
|
Flags = CommandBufferUsageFlags.OneTimeSubmitBit,
|
||||||
|
};
|
||||||
|
Check(
|
||||||
|
_vk.BeginCommandBuffer(_commandBuffer, &beginInfo),
|
||||||
|
"vkBeginCommandBuffer(compute)");
|
||||||
|
|
||||||
|
BeginDebugLabel(_commandBuffer, resources.DebugName);
|
||||||
|
if (isFirstBatch)
|
||||||
|
{
|
||||||
|
RecordTextureUploads(resources, PipelineStageFlags.ComputeShaderBit);
|
||||||
|
RecordStorageImagesForWrite(resources, PipelineStageFlags.ComputeShaderBit);
|
||||||
|
}
|
||||||
|
|
||||||
|
_vk.CmdBindPipeline(
|
||||||
_commandBuffer,
|
_commandBuffer,
|
||||||
PipelineBindPoint.Compute,
|
PipelineBindPoint.Compute,
|
||||||
resources.PipelineLayout,
|
resources.Pipeline);
|
||||||
0,
|
if (resources.DescriptorSet.Handle != 0)
|
||||||
1,
|
{
|
||||||
&descriptorSet,
|
var descriptorSet = resources.DescriptorSet;
|
||||||
0,
|
_vk.CmdBindDescriptorSets(
|
||||||
null);
|
_commandBuffer,
|
||||||
|
PipelineBindPoint.Compute,
|
||||||
|
resources.PipelineLayout,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
&descriptorSet,
|
||||||
|
0,
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
|
||||||
|
RecordChunkedComputeDispatch(_commandBuffer, work, zStart, zCount);
|
||||||
|
|
||||||
|
if (isLastBatch)
|
||||||
|
{
|
||||||
|
RecordStorageImagesForRead(resources, PipelineStageFlags.ComputeShaderBit);
|
||||||
|
}
|
||||||
|
|
||||||
|
EndDebugLabel(_commandBuffer);
|
||||||
|
Check(_vk.EndCommandBuffer(_commandBuffer), "vkEndCommandBuffer(compute)");
|
||||||
|
|
||||||
|
TraceVulkanShader(
|
||||||
|
$"vk.compute_submit cs=0x{work.ShaderAddress:X16} " +
|
||||||
|
$"batch={batchIndex}/{batchCount} z={zStart}..{zStart + zCount}");
|
||||||
|
if (isLastBatch)
|
||||||
|
{
|
||||||
|
SubmitGuestCommandBuffer(
|
||||||
|
commandBuffer,
|
||||||
|
resources,
|
||||||
|
GetTraceImages(resources));
|
||||||
|
submitted = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SubmitGuestCommandBufferAndWait(commandBuffer);
|
||||||
|
commandBuffer = default;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RecordChunkedComputeDispatch(_commandBuffer, work);
|
|
||||||
RecordStorageImagesForRead(resources, PipelineStageFlags.ComputeShaderBit);
|
|
||||||
EndDebugLabel(_commandBuffer);
|
|
||||||
|
|
||||||
Check(_vk.EndCommandBuffer(_commandBuffer), "vkEndCommandBuffer(compute)");
|
|
||||||
SubmitGuestCommandBuffer(
|
|
||||||
commandBuffer,
|
|
||||||
resources,
|
|
||||||
GetTraceImages(resources));
|
|
||||||
submitted = true;
|
|
||||||
MarkSampledImagesInitialized(resources);
|
MarkSampledImagesInitialized(resources);
|
||||||
MarkStorageImagesInitialized(resources, traceContents: false);
|
MarkStorageImagesInitialized(resources, traceContents: false);
|
||||||
TraceVulkanShader(
|
TraceVulkanShader(
|
||||||
$"vk.compute_dispatch groups={work.GroupCountX}x" +
|
$"vk.compute_dispatch groups={work.GroupCountX}x" +
|
||||||
$"{work.GroupCountY}x{work.GroupCountZ} " +
|
$"{work.GroupCountY}x{work.GroupCountZ} " +
|
||||||
$"textures={work.Textures.Count} cs=0x{work.ShaderAddress:X16}");
|
$"textures={work.Textures.Count} cs=0x{work.ShaderAddress:X16} " +
|
||||||
|
$"batches={batchCount}");
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine(
|
Console.Error.WriteLine(
|
||||||
$"[LOADER][ERROR] Vulkan compute dispatch failed: {exception.Message}");
|
$"[LOADER][ERROR] Vulkan compute dispatch failed " +
|
||||||
|
$"cs=0x{work.ShaderAddress:X16}: {exception.Message}");
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -3968,7 +4157,9 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
|
|
||||||
private void RecordChunkedComputeDispatch(
|
private void RecordChunkedComputeDispatch(
|
||||||
CommandBuffer commandBuffer,
|
CommandBuffer commandBuffer,
|
||||||
VulkanComputeGuestDispatch work)
|
VulkanComputeGuestDispatch work,
|
||||||
|
uint zStart,
|
||||||
|
uint zCount)
|
||||||
{
|
{
|
||||||
const uint maxWorkgroupsPerCommand = 4096;
|
const uint maxWorkgroupsPerCommand = 4096;
|
||||||
var yChunk = Math.Max(
|
var yChunk = Math.Max(
|
||||||
@@ -3978,7 +4169,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
maxWorkgroupsPerCommand / Math.Max(work.GroupCountX, 1u)));
|
maxWorkgroupsPerCommand / Math.Max(work.GroupCountX, 1u)));
|
||||||
var commandCount = 0u;
|
var commandCount = 0u;
|
||||||
|
|
||||||
for (var z = 0u; z < work.GroupCountZ; z++)
|
for (var z = zStart; z < zStart + zCount; z++)
|
||||||
{
|
{
|
||||||
for (var y = 0u; y < work.GroupCountY; y += yChunk)
|
for (var y = 0u; y < work.GroupCountY; y += yChunk)
|
||||||
{
|
{
|
||||||
@@ -4000,7 +4191,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
TraceVulkanShader(
|
TraceVulkanShader(
|
||||||
$"vk.compute_chunked cs=0x{work.ShaderAddress:X16} " +
|
$"vk.compute_chunked cs=0x{work.ShaderAddress:X16} " +
|
||||||
$"groups={work.GroupCountX}x{work.GroupCountY}x{work.GroupCountZ} " +
|
$"groups={work.GroupCountX}x{work.GroupCountY}x{work.GroupCountZ} " +
|
||||||
$"commands={commandCount} y_chunk={yChunk}");
|
$"z_range={zStart}..{zStart + zCount} commands={commandCount} y_chunk={yChunk}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4208,6 +4399,23 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
existing.MipLevels == mipLevels &&
|
existing.MipLevels == mipLevels &&
|
||||||
existing.Format == format)
|
existing.Format == format)
|
||||||
{
|
{
|
||||||
|
if (existing.RenderPass.Handle == 0)
|
||||||
|
{
|
||||||
|
var attachmentView = existing.MipViews.Length > 0
|
||||||
|
? existing.MipViews[0]
|
||||||
|
: existing.View;
|
||||||
|
var (promotedRenderPass, promotedFramebuffer) = CreateRenderPassAndFramebuffer(
|
||||||
|
existing.Format,
|
||||||
|
attachmentView,
|
||||||
|
existing.Width,
|
||||||
|
existing.Height);
|
||||||
|
existing.RenderPass = promotedRenderPass;
|
||||||
|
existing.Framebuffer = promotedFramebuffer;
|
||||||
|
var promotedName = GuestImageDebugName(target, format);
|
||||||
|
SetDebugName(ObjectType.RenderPass, promotedRenderPass.Handle, $"{promotedName} renderpass");
|
||||||
|
SetDebugName(ObjectType.Framebuffer, promotedFramebuffer.Handle, $"{promotedName} framebuffer");
|
||||||
|
}
|
||||||
|
|
||||||
return existing;
|
return existing;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4288,6 +4496,48 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
mipViews[mipLevel] = mipView;
|
mipViews[mipLevel] = mipView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (renderPass, framebuffer) = CreateRenderPassAndFramebuffer(
|
||||||
|
format,
|
||||||
|
mipViews[0],
|
||||||
|
target.Width,
|
||||||
|
target.Height);
|
||||||
|
|
||||||
|
var resource = new GuestImageResource
|
||||||
|
{
|
||||||
|
Address = target.Address,
|
||||||
|
Width = target.Width,
|
||||||
|
Height = target.Height,
|
||||||
|
MipLevels = mipLevels,
|
||||||
|
Format = format,
|
||||||
|
Image = image,
|
||||||
|
Memory = memory,
|
||||||
|
View = view,
|
||||||
|
MipViews = mipViews,
|
||||||
|
RenderPass = renderPass,
|
||||||
|
Framebuffer = framebuffer,
|
||||||
|
};
|
||||||
|
var debugName = GuestImageDebugName(target, format);
|
||||||
|
SetDebugName(ObjectType.Image, image.Handle, $"{debugName} image");
|
||||||
|
SetDebugName(ObjectType.ImageView, view.Handle, $"{debugName} view");
|
||||||
|
for (var mipLevel = 0; mipLevel < mipViews.Length; mipLevel++)
|
||||||
|
{
|
||||||
|
SetDebugName(
|
||||||
|
ObjectType.ImageView,
|
||||||
|
mipViews[mipLevel].Handle,
|
||||||
|
$"{debugName} mip{mipLevel}");
|
||||||
|
}
|
||||||
|
SetDebugName(ObjectType.RenderPass, renderPass.Handle, $"{debugName} renderpass");
|
||||||
|
SetDebugName(ObjectType.Framebuffer, framebuffer.Handle, $"{debugName} framebuffer");
|
||||||
|
_guestImages.Add(target.Address, resource);
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
private (RenderPass RenderPass, Framebuffer Framebuffer) CreateRenderPassAndFramebuffer(
|
||||||
|
Format format,
|
||||||
|
ImageView attachmentView,
|
||||||
|
uint width,
|
||||||
|
uint height)
|
||||||
|
{
|
||||||
var colorAttachment = new AttachmentDescription
|
var colorAttachment = new AttachmentDescription
|
||||||
{
|
{
|
||||||
Format = format,
|
Format = format,
|
||||||
@@ -4322,49 +4572,22 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
_vk.CreateRenderPass(_device, &renderPassInfo, null, out var renderPass),
|
_vk.CreateRenderPass(_device, &renderPassInfo, null, out var renderPass),
|
||||||
"vkCreateRenderPass(offscreen)");
|
"vkCreateRenderPass(offscreen)");
|
||||||
|
|
||||||
var attachment = mipViews[0];
|
var attachment = attachmentView;
|
||||||
var framebufferInfo = new FramebufferCreateInfo
|
var framebufferInfo = new FramebufferCreateInfo
|
||||||
{
|
{
|
||||||
SType = StructureType.FramebufferCreateInfo,
|
SType = StructureType.FramebufferCreateInfo,
|
||||||
RenderPass = renderPass,
|
RenderPass = renderPass,
|
||||||
AttachmentCount = 1,
|
AttachmentCount = 1,
|
||||||
PAttachments = &attachment,
|
PAttachments = &attachment,
|
||||||
Width = target.Width,
|
Width = width,
|
||||||
Height = target.Height,
|
Height = height,
|
||||||
Layers = 1,
|
Layers = 1,
|
||||||
};
|
};
|
||||||
Check(
|
Check(
|
||||||
_vk.CreateFramebuffer(_device, &framebufferInfo, null, out var framebuffer),
|
_vk.CreateFramebuffer(_device, &framebufferInfo, null, out var framebuffer),
|
||||||
"vkCreateFramebuffer(offscreen)");
|
"vkCreateFramebuffer(offscreen)");
|
||||||
|
|
||||||
var resource = new GuestImageResource
|
return (renderPass, framebuffer);
|
||||||
{
|
|
||||||
Address = target.Address,
|
|
||||||
Width = target.Width,
|
|
||||||
Height = target.Height,
|
|
||||||
MipLevels = mipLevels,
|
|
||||||
Format = format,
|
|
||||||
Image = image,
|
|
||||||
Memory = memory,
|
|
||||||
View = view,
|
|
||||||
MipViews = mipViews,
|
|
||||||
RenderPass = renderPass,
|
|
||||||
Framebuffer = framebuffer,
|
|
||||||
};
|
|
||||||
var debugName = GuestImageDebugName(target, format);
|
|
||||||
SetDebugName(ObjectType.Image, image.Handle, $"{debugName} image");
|
|
||||||
SetDebugName(ObjectType.ImageView, view.Handle, $"{debugName} view");
|
|
||||||
for (var mipLevel = 0; mipLevel < mipViews.Length; mipLevel++)
|
|
||||||
{
|
|
||||||
SetDebugName(
|
|
||||||
ObjectType.ImageView,
|
|
||||||
mipViews[mipLevel].Handle,
|
|
||||||
$"{debugName} mip{mipLevel}");
|
|
||||||
}
|
|
||||||
SetDebugName(ObjectType.RenderPass, renderPass.Handle, $"{debugName} renderpass");
|
|
||||||
SetDebugName(ObjectType.Framebuffer, framebuffer.Handle, $"{debugName} framebuffer");
|
|
||||||
_guestImages.Add(target.Address, resource);
|
|
||||||
return resource;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static uint ClampMipLevels(uint width, uint height, uint requestedMipLevels)
|
private static uint ClampMipLevels(uint width, uint height, uint requestedMipLevels)
|
||||||
|
|||||||
Reference in New Issue
Block a user