Vulkan backend (#2518)
* WIP Vulkan implementation * No need to initialize attributes on the SPIR-V backend anymore * Allow multithreading shaderc and vkCreateShaderModule You'll only really see the benefit here with threaded-gal or parallel shader cache compile. Fix shaderc multithreaded changes Thread safety for shaderc Options constructor Dunno how they managed to make a constructor not thread safe, but you do you. May avoid some freezes. * Support multiple levels/layers for blit. Fixes MK8D when scaled, maybe a few other games. AMD software "safe" blit not supported right now. * TextureStorage should hold a ref of the foreign storage, otherwise it might be freed while in use * New depth-stencil blit method for AMD * Workaround for AMD driver bug * Fix some tessellation related issues (still doesn't work?) * Submit command buffer before Texture GetData. (UE4 fix) * DrawTexture support * Fix BGRA on OpenGL backend * Fix rebase build break * Support format aliasing on SetImage * Fix uniform buffers being lost when bindings are out of order * Fix storage buffers being lost when bindings are out of order (also avoid allocations when changing bindings) * Use current command buffer for unscaled copy (perf) Avoids flushing commands and renting a command buffer when fulfilling copy dependencies and when games do unscaled copies. * Update to .net6 * Update Silk.NET to version 2.10.1 Somehow, massive performance boost. Seems like their vtable for looking up vulkan methods was really slow before. * Fix PrimitivesGenerated query, disable Transform Feedback queries for now Lets Splatoon 2 work on nvidia. (mostly) * Update counter queue to be similar to the OGL one Fixes softlocks when games had to flush counters. * Don't throw when ending conditional rendering for now This should be re-enabled when conditional rendering is enabled on nvidia etc. * Update findMSB/findLSB to match master's instruction enum * Fix triangle overlay on SMO, Captain Toad, maybe others? * Don't make Intel Mesa pay for Intel Windows bugs * Fix samplers with MinFilter Linear or Nearest (fixes New Super Mario Bros U Deluxe black borders) * Update Spv.Generator * Add alpha test emulation on shader (but no shader specialisation yet...) * Fix R4G4B4A4Unorm texture format permutation * Validation layers should be enabled for any log level other than None * Add barriers around vkCmdCopyImage Write->Read barrier for src image (we want to wait for a write to read it) Write->Read barrier for dst image (we want to wait for the copy to complete before use) * Be a bit more careful with texture access flags, since it can be used for anything * Device local mapping for all buffers May avoid issues with drivers with NVIDIA on linux/older gpus on windows when using large buffers (?) Also some performance things and fixes issues with opengl games loading textures weird. * Cleanup, disable device local buffers for now. * Add single queue support Multiqueue seems to be a bit more responsive on NVIDIA. Should fix texture flush on intel. AMD has been forced to single queue for an experiment. * Fix some validation errors around extended dynamic state * Remove Intel bug workaround, it was fixed on the latest driver * Use circular queue for checking consumption on command buffers Speeds up games that spam command buffers a little. Avoids checking multiple command buffers if multiple are active at once. * Use SupportBufferUpdater, add single layer flush * Fix counter queue leak when game decides to use host conditional rendering * Force device local storage for textures (fixes linux performance) * Port #3019 * Insert barriers around vkCmdBlitImage (may fix some amd flicker) * Fix transform feedback on Intel, gl_Position feedback and clears to inexistent depth buffers * Don't pause transform feedback for multi draw * Fix draw outside of render pass and missing capability * Workaround for wrong last attribute on AMD (affects FFVII, STRIKERS1945, probably more) * Better workaround for AMD vertex buffer size alignment issue * More instructions + fixes on SPIR-V backend * Allow custom aspect ratio on Vulkan * Correct GTK UI status bar positions * SPIR-V: Functions must always end with a return * SPIR-V: Fix ImageQuerySizeLod * SPIR-V: Set DepthReplacing execution mode when FragDepth is modified * SPIR-V: Implement LoopContinue IR instruction * SPIR-V: Geometry shader support * SPIR-V: Use correct binding number on storage buffers array * Reduce allocations for Spir-v serialization Passes BinaryWriter instead of the stream to Write and WriteOperand - Removes creation of BinaryWriter for each instruction - Removes allocations for literal string * Some optimizations to Spv.Generator - Dictionary for lookups of type declarations, constants, extinst - LiteralInteger internal data format -> ushort - Deterministic HashCode implementation to avoid spirv result not being the same between runs - Inline operand list instead of List<T>, falls back to array if many operands. (large performance boost) TODO: improve instruction allocation, structured program creator, ssa? * Pool Spv.Generator resources, cache delegates, spv opts - Pools for Instructions and LiteralIntegers. Can be passed in when creating the generator module. - NewInstruction is called instead of new Instruction() - Ryujinx SpirvGenerator passes in some pools that are static. The idea is for these to be shared between threads eventually. - Estimate code size when creating the output MemoryStream - LiteralInteger pools using ThreadStatic pools that are initialized before and after creation... not sure of a better way since the way these are created is via implicit cast. Also, cache delegates for Spv.Generator for functions that are passed around to GenerateBinary etc, since passing the function raw creates a delegate on each call. TODO: update python spv cs generator to make the coregrammar with NewInstruction and the `params` overloads. * LocalDefMap for Ssa Rewriter Rather than allocating a large array of all registers for each block in the shader, allocate one array of all registers and clear it between blocks. Reduces allocations in the shader translator. * SPIR-V: Transform feedback support * SPIR-V: Fragment shader interlock support (and image coherency) * SPIR-V: Add early fragment tests support * SPIR-V: Implement SwizzleAdd, add missing Triangles ExecutionMode for geometry shaders, remove SamplerType field from TextureMeta * Don't pass depth clip state right now (fix decals) Explicitly disabling it is incorrect. OpenGL currently automatically disables based on depth clamp, which is the behaviour if this state is omitted. * Multisampling support * Multisampling: Use resolve if src samples count > dst samples count * Multisampling: We can only resolve for unscaled copies * SPIR-V: Only add FSI exec mode if used. * SPIR-V: Use ConstantComposite for Texture Offset Vector Fixes a bunch of freezes with SPIR-V on AMD hardware, and validation errors. Note: Obviously assumes input offsets are constant, which they currently are. * SPIR-V: Don't OpReturn if we already OpExit'ed Fixes spir-v parse failure and stack smashing in RADV (obviously you still need bolist) * SPIR-V: Only use input attribute type for input attributes Output vertex attributes should always be of type float. * Multithreaded Pipeline Compilation * Address some feedback * Make this 32 * Update topology with GpuAccessorState * Cleanup for merge (note: disables spir-v) * Make more robust to shader compilation failure - Don't freeze when GLSL compilation fails - Background SPIR-V pipeline compile failure results in skipped draws, similar to GLSL compilation failure. * Fix Multisampling * Only update fragment scale count if a vertex texture needs a scale. Fixes a performance regression introduced by texture scaling in the vertex stage where support buffer updates would be very frequent, even at 1x, if any textures were used on the vertex stage. This check doesn't exactly look cheap (a flag in the shader stage would probably be preferred), but it is much cheaper than uploading scales in both vulkan and opengl, so it will do for now. * Use a bitmap to do granular tracking for buffer uploads. This path is only taken if the much faster check of "is the buffer rented at all" is triggered, so it doesn't actually end up costing too much, and the time saved by not ending render passes (and on gpu for not waiting on barriers) is probably helpful. Avoids ending render passes to update buffer data (not all the time) - 140-180 to 35-45 in SMO metro kingdom (these updates are in the UI) - Very variable 60-150(!) to 16-25 in mario kart 8 (these updates are in the UI) As well as allowing more data to be preloaded persistently, this will also allow more data to be loaded in the preload buffer, which should be faster as it doesn't need to insert barriers between draws. (and on tbdr, does not need to flush and reload tile memory) Improves performance in GPU limited scenarios. Should notably improve performance on TBDR gpus. Still a lot more to do here. * Copy query results after RP ends, rather than ending to copy We need to end the render pass to get the data (submit command buffer) anyways... Reduces render passes created in games that use queries. * Rework Query stuff a bit to avoid render pass end Tries to reset returned queries in background when possible, rather than ending the render pass. Still ends render pass when resetting a counter after draws, but maybe that can be solved too. (by just pulling an empty object off the pool?) * Remove unnecessary lines Was for testing * Fix validation error for query reset Need to think of a better way to do this. * SPIR-V: Fix SwizzleAdd and some validation errors * SPIR-V: Implement attribute indexing and StoreAttribute * SPIR-V: Fix TextureSize for MS and Buffer sampler types * Fix relaunch issues * SPIR-V: Implement LogicalExclusiveOr * SPIR-V: Constant buffer indexing support * Ignore unsupported attributes rather than throwing (matches current GLSL behaviour) * SPIR-V: Implement tessellation support * SPIR-V: Geometry shader passthrough support * SPIR-V: Implement StoreShader8/16 and StoreStorage8/16 * SPIR-V: Resolution scale support and fix TextureSample multisample with LOD bug * SPIR-V: Fix field index for scale count * SPIR-V: Fix another case of wrong field index * SPIRV/GLSL: More scaling related fixes * SPIR-V: Fix ImageLoad CompositeExtract component type * SPIR-V: Workaround for Intel FrontFacing bug * Enable SPIR-V backend by default * Allow null samplers (samplers are not required when only using texelFetch to access the texture) * Fix some validation errors related to texel block view usage flag and invalid image barrier base level * Use explicit subgroup size if we can (might fix some block flickering on AMD) * Take componentMask and scissor into account when clearing framebuffer attachments * Add missing barriers around CmdFillBuffer (fixes Monster Hunter Rise flickering on NVIDIA) * Use ClampToEdge for Clamp sampler address mode on Vulkan (fixes Hollow Knight) Clamp is unsupported on Vulkan, but ClampToEdge behaves almost the same. ClampToBorder on the other hand (which was being used before) is pretty different * Shader specialization for new Vulkan required state (fixes remaining alpha test issues, vertex stretching on AMD on Crash Bandicoot, etc) * Check if the subgroup size is supported before passing a explicit size * Only enable ShaderFloat64 if the GPU supports it * We don't need to recompile shaders if alpha test state changed but alpha test is disabled * Enable shader cache on Vulkan and implement MultiplyHighS32/U32 on SPIR-V (missed those before) * Fix pipeline state saving before it is updated. This should fix a few warnings and potential stutters due to bad pipeline states being saved in the cache. You may need to clear your guest cache. * Allow null samplers on OpenGL backend * _unit0Sampler should be set only for binding 0 * Remove unused PipelineConverter format variable (was causing IOR) * Raise textures limit to 64 on Vulkan * No need to pack the shader binaries if shader cache is disabled * Fix backbuffer not being cleared and scissor not being re-enabled on OpenGL * Do not clear unbound framebuffer color attachments * Geometry shader passthrough emulation * Consolidate UpdateDepthMode and GetDepthMode implementation * Fix A1B5G5R5 texture format and support R4G4 on Vulkan * Add barrier before use of some modified images * Report 32 bit query result on AMD windows (smo issue) * Add texture recompression support (disabled for now) It recompresses ASTC textures into BC7, which might reduce VRAM usage significantly on games that uses ASTC textures * Do not report R4G4 format as supported on Vulkan It was causing mario head to become white on Super Mario 64 (???) * Improvements to -1 to 1 depth mode. - Transformation is only applied on the last stage in the vertex pipeline. - Should fix some issues with geometry and tessellation (hopefully) - Reading back FragCoord Z on fragment will transform back to -1 to 1. * Geometry Shader index count from ThreadsPerInputPrimitive Generally fixes SPIR-V emitting too many triangles, may change games in OpenGL * Remove gl_FragDepth scaling This is always 0-1; the other two issues were causing the problems. Fixes regression with Xenoblade. * Add Gl StencilOp enum values to Vulkan * Update guest cache to v1.1 (due to specialization state changes) This will explode your shader cache from earlier vulkan build, but it must be done. 😔 * Vulkan/SPIR-V support for viewport inverse * Fix typo * Don't create query pools for unsupported query types * Return of the Vector Indexing Bug One day, everyone will get this right. * Check for transform feedback query support Sometimes transform feedback is supported without the query type. * Fix gl_FragCoord.z transformation FragCoord.z is always in 0-1, even when the real depth range is -1 to 1. Turns out the only bug was geo and tess stage outputs. Fixes Pokemon Sword/Shield, possibly others. * Fix Avalonia Rebase Vulkan is currently not available on Avalonia, but the build does work and you can use opengl. * Fix headless build * Add support for BC6 and BC7 decompression, decompress all BC formats if they are not supported by the host * Fix BCn 4/5 conversion, GetTextureTarget BCn 4/5 could generate invalid data when a line's size in bytes was not divisible by 4, which both backends expect. GetTextureTarget was not creating a view with the replacement format. * Fix dependency * Fix inverse viewport transform vector type on SPIR-V * Do not require null descriptors support * If MultiViewport is not supported, do not try to set more than one viewport/scissor * Bounds check on bitmap add. * Flush queries on attachment change rather than program change Occlusion queries are usually used in a depth only pass so the attachments changing is a better indication of the query block ending. Write mask changes are also considered since some games do depth only pass by setting 0 write mask on all the colour targets. * Add support for avalonia (#6) * add avalonia support * only lock around skia flush * addressed review * cleanup * add fallback size if avalonia attempts to render but the window size is 0. read desktop scale after enabling dpi check * fix getting window handle on linux. skip render is size is 0 * Combine non-buffer with buffer image descriptor sets * Support multisample texture copy with automatic resolve on Vulkan * Remove old CompileShader methods from the Vulkan backend * Add minimal pipeline layouts that only contains used bindings They are used by helper shaders, the intention is avoiding needing to recompile the shaders (from GLSL to SPIR-V) if the bindings changes on the translated guest shaders * Pre-compile helper shader as SPIR-V, and some fixes * Remove pre-compiled shaderc binary for Windows as its no longer needed by default * Workaround RADV crash Enabling the descriptor indexing extension, even if it is not used, forces the radv driver to use "bolist". * Use RobustBufferAccess on NVIDIA gpus Avoids the SMO waterfall triangle on older NVIDIA gpus. * Implement GPU selector and expose texture recompression on the UI and config * Fix and enable background compute shader compilation Also disables warnings from shader cache pipeline misses. * Fix error due to missing subpass dependency when Attachment Write -> Shader Read barriers are added * If S8D24 is not supported, use D32FS8 * Ensure all fences are destroyed on dispose * Pre-allocate arrays up front on DescriptorSetUpdater, allows the removal of some checks * Add missing clear layer parameter after rebase * Use selected gpu from config for avalonia (#7) * use configured device * address review * Fix D32S8 copy workaround (AMD) Fixes water in Pokemon Legends Arceus on AMD GPUs. Possibly fixes other things. * Use push descriptors for uniform buffer updates (disabled for now) * Push descriptor support check, buffer redundancy checks Should make push descriptors faster, needs more testing though. * Increase light command buffer pool to 2 command buffers, throw rather than returning invalid cbs * Adjust bindings array sizes * Force submit command buffers if memory in use by its resources is high * Add workaround for AMD GCN cubemap view sins `ImageCreateCubeCompatibleBit` seems to generally break 2D array textures with mipmaps... even if they are eventually aliased as a cubemap with mipmaps. Forcing a copy here works around the issue. This could be used in future if enabling this bit reduces performance on certain GPUs. (mobile class is generally a worry) Currently also enabled on Linux as I don't know if they managed to dodge this bug (someone please tell me). Not enabled on Vega at the moment, but easy to add if the issue is there. * Add mobile, non-RX variants to the GCN regex. Also make sure that the 3 digit ones only include numbers starting with 7 or 8. * Increase image limit per stage from 8 to 16 Xenoblade Chronicles 2 was hiting the limit of 8 * Minor code cleanup * Fix NRE caused by SupportBufferUpdater calling pipeline ClearBuffer * Add gpu selector to Avalonia (#8) * Add gpu selector to avalonia settings * show backend label on window * some fixes * address review * Minor changes to the Avalonia UI * Update graphics window UI and locales. (#9) * Update xaml and update locales * locale updates Did my best here but likely needs to be checked by native speakers, especially the use of ampersands in greek, russian and turkish? * Fix locales with more (?) correct translations. * add separator to render widget * fix spanish and portuguese * Add new IdList, replaces buffer list that could not remove elements and had unbounded growth * Don't crash the settings window if Vulkan is not supported * Fix Actions menu not being clickable on GTK UI after relaunch * Rename VulkanGraphicsDevice to VulkanRenderer and Renderer to OpenGLRenderer * Fix IdList and make it not thread safe * Revert useless OpenGL format table changes * Fix headless project build * List throws ArgumentOutOfRangeException * SPIR-V: Fix tessellation * Increase shader cache version due to tessellation fix * Reduce number of Sync objects created (improves perf in some specific titles) * Fix vulkan validation errors for NPOT compressed upload and GCN workaround. * Add timestamp to the shader cache and force rebuild if host cache is outdated * Prefer Mail box present mode for popups (#11) * Prefer Mail box present mode * fix debug * switch present mode when vsync is toggled * only disable vsync on the main window * SPIR-V: Fix geometry shader input load with transform feedback * BC7 Encoder: Prefer more precision on alpha rather than RGB when alpha is 0 * Fix Avalonia build * Address initial PR feedback * Only set transform feedback outputs on last vertex stage * Address riperiperi PR feedback * Remove outdated comment * Remove unused constructor * Only throw for negative results * Throw for QueueSubmit and other errors No point in delaying the inevitable * Transform feedback decorations inside gl_PerVertex struct breaks the NVIDIA compiler * Fix some resolution scale issues * No need for two UpdateScale calls * Fix comments on SPIR-V generator project * Try to fix shader local memory size On DOOM, a shader is using local memory, but both Low and High size are 0, CRS size is 1536, it seems to store on that region? * Remove RectangleF that is now unused * Fix ImageGather with multiple offsets Needs ImageGatherExtended capability, and must use `ConstantComposite` instead of `CompositeConstruct` * Address PR feedback from jD in all projects except Avalonia * Address most of jD PR feedback on Avalonia * Remove unsafe * Fix VulkanSkiaGpu * move present mode request out of Create Swapchain method * split more parts of create swapchain * addressed reviews * addressed review * Address second batch of jD PR feedback * Fix buffer <-> image copy row length and height alignment AlignUp helper does not support NPOT alignment, and ASTC textures can have NPOT block sizes * Better fix for NPOT alignment issue * Use switch expressions on Vulkan EnumConversion Thanks jD * Fix Avalonia build * Add Vulkan selection prompt on startup * Grammar fixes on Vulkan prompt message * Add missing Vulkan migration flag Co-authored-by: riperiperi <rhy3756547@hotmail.com> Co-authored-by: Emmanuel Hansen <emmausssss@gmail.com> Co-authored-by: MutantAura <44103205+MutantAura@users.noreply.github.com>
This commit is contained in:
@ -18,7 +18,7 @@ namespace Ryujinx.Graphics.Gpu
|
||||
/// Maximum number of compute storage buffers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The maximum number of storage buffers is API limited, the hardware supports a unlimited amount.
|
||||
/// The maximum number of storage buffers is API limited, the hardware supports an unlimited amount.
|
||||
/// </remarks>
|
||||
public const int TotalCpStorageBuffers = 16;
|
||||
|
||||
@ -31,7 +31,7 @@ namespace Ryujinx.Graphics.Gpu
|
||||
/// Maximum number of graphics storage buffers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The maximum number of storage buffers is API limited, the hardware supports a unlimited amount.
|
||||
/// The maximum number of storage buffers is API limited, the hardware supports an unlimited amount.
|
||||
/// </remarks>
|
||||
public const int TotalGpStorageBuffers = 16;
|
||||
|
||||
@ -40,6 +40,22 @@ namespace Ryujinx.Graphics.Gpu
|
||||
/// </summary>
|
||||
public const int TotalTransformFeedbackBuffers = 4;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of textures on a single shader stage.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The maximum number of textures is API limited, the hardware supports an unlimited amount.
|
||||
/// </remarks>
|
||||
public const int TotalTextures = 32;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of images on a single shader stage.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The maximum number of images is API limited, the hardware supports an unlimited amount.
|
||||
/// </remarks>
|
||||
public const int TotalImages = 8;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of render target color buffers.
|
||||
/// </summary>
|
||||
@ -53,7 +69,7 @@ namespace Ryujinx.Graphics.Gpu
|
||||
/// <summary>
|
||||
/// Maximum number of vertex attributes.
|
||||
/// </summary>
|
||||
public const int TotalVertexAttribs = 16;
|
||||
public const int TotalVertexAttribs = 16; // FIXME: Should be 32, but OpenGL only supports 16.
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of vertex buffers.
|
||||
|
@ -238,8 +238,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
||||
_channel.TextureManager.SetComputeMaxBindings(maxTextureBinding, maxImageBinding);
|
||||
|
||||
// Should never return false for mismatching spec state, since the shader was fetched above.
|
||||
_channel.TextureManager.CommitComputeBindings(cs.SpecializationState);
|
||||
|
||||
_channel.TextureManager.CommitComputeBindings(cs.SpecializationState);
|
||||
|
||||
_channel.BufferManager.CommitComputeBindings();
|
||||
|
||||
_context.Renderer.Pipeline.DispatchCompute(qmd.CtaRasterWidth, qmd.CtaRasterHeight, qmd.CtaRasterDepth);
|
||||
|
@ -147,6 +147,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
||||
int xCount = (int)_state.State.LineLengthIn;
|
||||
int yCount = (int)_state.State.LineCount;
|
||||
|
||||
_3dEngine.CreatePendingSyncs();
|
||||
_3dEngine.FlushUboDirty();
|
||||
|
||||
if (copy2D)
|
||||
|
@ -15,6 +15,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
|
||||
private readonly GPFifoProcessor _parent;
|
||||
private readonly DeviceState<GPFifoClassState> _state;
|
||||
|
||||
private int _previousSubChannel;
|
||||
private bool _createSyncPending;
|
||||
|
||||
private const int MacrosCount = 0x80;
|
||||
|
||||
// Note: The size of the macro memory is unknown, we just make
|
||||
@ -48,6 +51,18 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
|
||||
_macroCode = new int[MacroCodeSize];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create any syncs from WaitForIdle command that are currently pending.
|
||||
/// </summary>
|
||||
public void CreatePendingSyncs()
|
||||
{
|
||||
if (_createSyncPending)
|
||||
{
|
||||
_createSyncPending = false;
|
||||
_context.CreateHostSyncIfNeeded(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads data from the class registers.
|
||||
/// </summary>
|
||||
@ -158,7 +173,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
|
||||
_parent.PerformDeferredDraws();
|
||||
_context.Renderer.Pipeline.Barrier();
|
||||
|
||||
_context.CreateHostSyncIfNeeded(false);
|
||||
_createSyncPending = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -65,7 +65,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
|
||||
_channel = channel;
|
||||
|
||||
_fifoClass = new GPFifoClass(context, this);
|
||||
_3dClass = new ThreedClass(context, channel);
|
||||
_3dClass = new ThreedClass(context, channel, _fifoClass);
|
||||
_computeClass = new ComputeClass(context, channel, _3dClass);
|
||||
_i2mClass = new InlineToMemoryClass(context, channel);
|
||||
_2dClass = new TwodClass(channel);
|
||||
|
@ -559,7 +559,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
scissorH = (int)MathF.Ceiling(scissorH * scale);
|
||||
}
|
||||
|
||||
_context.Renderer.Pipeline.SetScissor(0, true, scissorX, scissorY, scissorW, scissorH);
|
||||
Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[]
|
||||
{
|
||||
new Rectangle<int>(scissorX, scissorY, scissorW, scissorH)
|
||||
};
|
||||
|
||||
_context.Renderer.Pipeline.SetScissors(scissors);
|
||||
}
|
||||
|
||||
if (clipMismatch)
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
@ -15,11 +16,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// </summary>
|
||||
class StateUpdater
|
||||
{
|
||||
public const int ShaderStateIndex = 0;
|
||||
public const int RasterizerStateIndex = 1;
|
||||
public const int ScissorStateIndex = 2;
|
||||
public const int VertexBufferStateIndex = 3;
|
||||
public const int PrimitiveRestartStateIndex = 4;
|
||||
public const int ShaderStateIndex = 16;
|
||||
public const int RasterizerStateIndex = 15;
|
||||
public const int ScissorStateIndex = 18;
|
||||
public const int VertexBufferStateIndex = 0;
|
||||
public const int PrimitiveRestartStateIndex = 12;
|
||||
|
||||
private readonly GpuContext _context;
|
||||
private readonly GpuChannel _channel;
|
||||
@ -31,6 +32,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
private readonly ShaderProgramInfo[] _currentProgramInfo;
|
||||
private ShaderSpecializationState _shaderSpecState;
|
||||
|
||||
private ProgramPipelineState _pipeline;
|
||||
|
||||
private bool _vtgWritesRtLayer;
|
||||
private byte _vsClipDistancesWritten;
|
||||
|
||||
@ -54,7 +57,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
_drawState = drawState;
|
||||
_currentProgramInfo = new ShaderProgramInfo[Constants.ShaderStages];
|
||||
|
||||
// ShaderState must be the first, as other state updates depends on information from the currently bound shader.
|
||||
// ShaderState must be updated after other state updates, as pipeline state is sent to the backend when compiling new shaders.
|
||||
// Render target state must appear after shader state as it depends on information from the currently bound shader.
|
||||
// Rasterizer and scissor states are checked by render target clear, their indexes
|
||||
// must be updated on the constants "RasterizerStateIndex" and "ScissorStateIndex" if modified.
|
||||
// The vertex buffer state may be forced dirty when a indexed draw starts, the "VertexBufferStateIndex"
|
||||
@ -62,53 +66,39 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
// The order of the other state updates doesn't matter.
|
||||
_updateTracker = new StateUpdateTracker<ThreedClassState>(new[]
|
||||
{
|
||||
new StateUpdateCallbackEntry(UpdateShaderState,
|
||||
nameof(ThreedClassState.ShaderBaseAddress),
|
||||
nameof(ThreedClassState.ShaderState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateRasterizerState, nameof(ThreedClassState.RasterizeEnable)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateScissorState,
|
||||
nameof(ThreedClassState.ScissorState),
|
||||
nameof(ThreedClassState.ScreenScissorState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateVertexBufferState,
|
||||
nameof(ThreedClassState.VertexBufferDrawState),
|
||||
nameof(ThreedClassState.VertexBufferInstanced),
|
||||
nameof(ThreedClassState.VertexBufferState),
|
||||
nameof(ThreedClassState.VertexBufferEndAddress)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdatePrimitiveRestartState,
|
||||
nameof(ThreedClassState.PrimitiveRestartDrawArrays),
|
||||
nameof(ThreedClassState.PrimitiveRestartState)),
|
||||
new StateUpdateCallbackEntry(UpdateVertexAttribState, nameof(ThreedClassState.VertexAttribState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateTessellationState,
|
||||
nameof(ThreedClassState.TessOuterLevel),
|
||||
nameof(ThreedClassState.TessInnerLevel),
|
||||
nameof(ThreedClassState.PatchVertices)),
|
||||
new StateUpdateCallbackEntry(UpdateBlendState,
|
||||
nameof(ThreedClassState.BlendIndependent),
|
||||
nameof(ThreedClassState.BlendConstant),
|
||||
nameof(ThreedClassState.BlendStateCommon),
|
||||
nameof(ThreedClassState.BlendEnableCommon),
|
||||
nameof(ThreedClassState.BlendEnable),
|
||||
nameof(ThreedClassState.BlendState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateTfBufferState, nameof(ThreedClassState.TfBufferState)),
|
||||
new StateUpdateCallbackEntry(UpdateUserClipState, nameof(ThreedClassState.ClipDistanceEnable)),
|
||||
new StateUpdateCallbackEntry(UpdateFaceState, nameof(ThreedClassState.FaceState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateRenderTargetState,
|
||||
nameof(ThreedClassState.RtColorState),
|
||||
nameof(ThreedClassState.RtDepthStencilState),
|
||||
nameof(ThreedClassState.RtControl),
|
||||
nameof(ThreedClassState.RtDepthStencilSize),
|
||||
nameof(ThreedClassState.RtDepthStencilEnable)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateDepthClampState, nameof(ThreedClassState.ViewVolumeClipControl)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateAlphaTestState,
|
||||
nameof(ThreedClassState.AlphaTestEnable),
|
||||
nameof(ThreedClassState.AlphaTestRef),
|
||||
nameof(ThreedClassState.AlphaTestFunc)),
|
||||
new StateUpdateCallbackEntry(UpdateStencilTestState,
|
||||
nameof(ThreedClassState.StencilBackMasks),
|
||||
nameof(ThreedClassState.StencilTestState),
|
||||
nameof(ThreedClassState.StencilBackTestState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateDepthTestState,
|
||||
nameof(ThreedClassState.DepthTestEnable),
|
||||
nameof(ThreedClassState.DepthWriteEnable),
|
||||
nameof(ThreedClassState.DepthTestFunc)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateTessellationState,
|
||||
nameof(ThreedClassState.TessOuterLevel),
|
||||
nameof(ThreedClassState.TessInnerLevel),
|
||||
nameof(ThreedClassState.PatchVertices)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateViewportTransform,
|
||||
nameof(ThreedClassState.DepthMode),
|
||||
nameof(ThreedClassState.ViewportTransform),
|
||||
@ -116,6 +106,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
nameof(ThreedClassState.YControl),
|
||||
nameof(ThreedClassState.ViewportTransformEnable)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateLogicOpState, nameof(ThreedClassState.LogicOpState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateDepthClampState, nameof(ThreedClassState.ViewVolumeClipControl)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdatePolygonMode,
|
||||
nameof(ThreedClassState.PolygonModeFront),
|
||||
nameof(ThreedClassState.PolygonModeBack)),
|
||||
@ -126,21 +120,46 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
nameof(ThreedClassState.DepthBiasUnits),
|
||||
nameof(ThreedClassState.DepthBiasClamp)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateStencilTestState,
|
||||
nameof(ThreedClassState.StencilBackMasks),
|
||||
nameof(ThreedClassState.StencilTestState),
|
||||
nameof(ThreedClassState.StencilBackTestState)),
|
||||
new StateUpdateCallbackEntry(UpdatePrimitiveRestartState, nameof(ThreedClassState.PrimitiveRestartState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateLineState,
|
||||
nameof(ThreedClassState.LineWidthSmooth),
|
||||
nameof(ThreedClassState.LineSmoothEnable)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateRtColorMask,
|
||||
nameof(ThreedClassState.RtColorMaskShared),
|
||||
nameof(ThreedClassState.RtColorMask)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateRasterizerState, nameof(ThreedClassState.RasterizeEnable)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateShaderState,
|
||||
nameof(ThreedClassState.ShaderBaseAddress),
|
||||
nameof(ThreedClassState.ShaderState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateRenderTargetState,
|
||||
nameof(ThreedClassState.RtColorState),
|
||||
nameof(ThreedClassState.RtDepthStencilState),
|
||||
nameof(ThreedClassState.RtControl),
|
||||
nameof(ThreedClassState.RtDepthStencilSize),
|
||||
nameof(ThreedClassState.RtDepthStencilEnable)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateScissorState,
|
||||
nameof(ThreedClassState.ScissorState),
|
||||
nameof(ThreedClassState.ScreenScissorState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateTfBufferState, nameof(ThreedClassState.TfBufferState)),
|
||||
new StateUpdateCallbackEntry(UpdateUserClipState, nameof(ThreedClassState.ClipDistanceEnable)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateAlphaTestState,
|
||||
nameof(ThreedClassState.AlphaTestEnable),
|
||||
nameof(ThreedClassState.AlphaTestRef),
|
||||
nameof(ThreedClassState.AlphaTestFunc)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateSamplerPoolState,
|
||||
nameof(ThreedClassState.SamplerPoolState),
|
||||
nameof(ThreedClassState.SamplerIndex)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateTexturePoolState, nameof(ThreedClassState.TexturePoolState)),
|
||||
new StateUpdateCallbackEntry(UpdateVertexAttribState, nameof(ThreedClassState.VertexAttribState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateLineState,
|
||||
nameof(ThreedClassState.LineWidthSmooth),
|
||||
nameof(ThreedClassState.LineSmoothEnable)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdatePointState,
|
||||
nameof(ThreedClassState.PointSize),
|
||||
@ -152,22 +171,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
nameof(ThreedClassState.IndexBufferState),
|
||||
nameof(ThreedClassState.IndexBufferCount)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateFaceState, nameof(ThreedClassState.FaceState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateRtColorMask,
|
||||
nameof(ThreedClassState.RtColorMaskShared),
|
||||
nameof(ThreedClassState.RtColorMask)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateBlendState,
|
||||
nameof(ThreedClassState.BlendIndependent),
|
||||
nameof(ThreedClassState.BlendConstant),
|
||||
nameof(ThreedClassState.BlendStateCommon),
|
||||
nameof(ThreedClassState.BlendEnableCommon),
|
||||
nameof(ThreedClassState.BlendEnable),
|
||||
nameof(ThreedClassState.BlendState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateLogicOpState, nameof(ThreedClassState.LogicOpState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateMultisampleState,
|
||||
nameof(ThreedClassState.AlphaToCoverageDitherEnable),
|
||||
nameof(ThreedClassState.MultisampleControl))
|
||||
@ -324,6 +327,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// </summary>
|
||||
private void UpdateTessellationState()
|
||||
{
|
||||
_pipeline.PatchControlPoints = (uint)_state.State.PatchVertices;
|
||||
|
||||
_context.Renderer.Pipeline.SetPatchParameters(
|
||||
_state.State.PatchVertices,
|
||||
_state.State.TessOuterLevel.ToSpan(),
|
||||
@ -356,6 +361,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
private void UpdateRasterizerState()
|
||||
{
|
||||
bool enable = _state.State.RasterizeEnable;
|
||||
_pipeline.RasterizerDiscard = !enable;
|
||||
_context.Renderer.Pipeline.SetRasterizerDiscard(!enable);
|
||||
}
|
||||
|
||||
@ -497,11 +503,21 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// </summary>
|
||||
public void UpdateScissorState()
|
||||
{
|
||||
const int MinX = 0;
|
||||
const int MinY = 0;
|
||||
const int MaxW = 0xffff;
|
||||
const int MaxH = 0xffff;
|
||||
|
||||
Span<Rectangle<int>> regions = stackalloc Rectangle<int>[Constants.TotalViewports];
|
||||
|
||||
for (int index = 0; index < Constants.TotalViewports; index++)
|
||||
{
|
||||
ScissorState scissor = _state.State.ScissorState[index];
|
||||
|
||||
bool enable = scissor.Enable && (scissor.X1 != 0 || scissor.Y1 != 0 || scissor.X2 != 0xffff || scissor.Y2 != 0xffff);
|
||||
bool enable = scissor.Enable && (scissor.X1 != MinX ||
|
||||
scissor.Y1 != MinY ||
|
||||
scissor.X2 != MaxW ||
|
||||
scissor.Y2 != MaxH);
|
||||
|
||||
if (enable)
|
||||
{
|
||||
@ -531,13 +547,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
height = (int)MathF.Ceiling(height * scale);
|
||||
}
|
||||
|
||||
_context.Renderer.Pipeline.SetScissor(index, true, x, y, width, height);
|
||||
regions[index] = new Rectangle<int>(x, y, width, height);
|
||||
}
|
||||
else
|
||||
{
|
||||
_context.Renderer.Pipeline.SetScissor(index, false, 0, 0, 0, 0);
|
||||
regions[index] = new Rectangle<int>(MinX, MinY, MaxW, MaxH);
|
||||
}
|
||||
}
|
||||
|
||||
_context.Renderer.Pipeline.SetScissors(regions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -547,7 +565,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
private void UpdateDepthClampState()
|
||||
{
|
||||
ViewVolumeClipControl clip = _state.State.ViewVolumeClipControl;
|
||||
_context.Renderer.Pipeline.SetDepthClamp((clip & ViewVolumeClipControl.DepthClampDisabled) == 0);
|
||||
bool clamp = (clip & ViewVolumeClipControl.DepthClampDisabled) == 0;
|
||||
|
||||
_pipeline.DepthClampEnable = clamp;
|
||||
_context.Renderer.Pipeline.SetDepthClamp(clamp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -566,10 +587,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// </summary>
|
||||
private void UpdateDepthTestState()
|
||||
{
|
||||
_context.Renderer.Pipeline.SetDepthTest(new DepthTestDescriptor(
|
||||
DepthTestDescriptor descriptor = new DepthTestDescriptor(
|
||||
_state.State.DepthTestEnable,
|
||||
_state.State.DepthWriteEnable,
|
||||
_state.State.DepthTestFunc));
|
||||
_state.State.DepthTestFunc);
|
||||
|
||||
_pipeline.DepthTest = descriptor;
|
||||
_context.Renderer.Pipeline.SetDepthTest(descriptor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -596,7 +620,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
ref var scissor = ref _state.State.ScreenScissorState;
|
||||
|
||||
float rScale = _channel.TextureManager.RenderTargetScale;
|
||||
var scissorRect = new RectangleF(0, 0, (scissor.X + scissor.Width) * rScale, (scissor.Y + scissor.Height) * rScale);
|
||||
var scissorRect = new Rectangle<float>(0, 0, (scissor.X + scissor.Width) * rScale, (scissor.Y + scissor.Height) * rScale);
|
||||
|
||||
viewports[index] = new Viewport(scissorRect, ViewportSwizzle.PositiveX, ViewportSwizzle.PositiveY, ViewportSwizzle.PositiveZ, ViewportSwizzle.PositiveW, 0, 1);
|
||||
continue;
|
||||
@ -633,7 +657,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
height *= scale;
|
||||
}
|
||||
|
||||
RectangleF region = new RectangleF(x, y, width, height);
|
||||
Rectangle<float> region = new Rectangle<float>(x, y, width, height);
|
||||
|
||||
ViewportSwizzle swizzleX = transform.UnpackSwizzleX();
|
||||
ViewportSwizzle swizzleY = transform.UnpackSwizzleY();
|
||||
@ -653,7 +677,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
viewports[index] = new Viewport(region, swizzleX, swizzleY, swizzleZ, swizzleW, depthNear, depthFar);
|
||||
}
|
||||
|
||||
_context.Renderer.Pipeline.SetViewports(0, viewports, disableTransform);
|
||||
_context.Renderer.Pipeline.SetDepthMode(GetDepthMode());
|
||||
_context.Renderer.Pipeline.SetViewports(viewports, disableTransform);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -661,37 +686,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// </summary>
|
||||
private void UpdateDepthMode()
|
||||
{
|
||||
ref var transform = ref _state.State.ViewportTransform[0];
|
||||
ref var extents = ref _state.State.ViewportExtents[0];
|
||||
|
||||
DepthMode depthMode;
|
||||
|
||||
if (!float.IsInfinity(extents.DepthNear) &&
|
||||
!float.IsInfinity(extents.DepthFar) &&
|
||||
(extents.DepthFar - extents.DepthNear) != 0)
|
||||
{
|
||||
// Try to guess the depth mode being used on the high level API
|
||||
// based on current transform.
|
||||
// It is setup like so by said APIs:
|
||||
// If depth mode is ZeroToOne:
|
||||
// TranslateZ = Near
|
||||
// ScaleZ = Far - Near
|
||||
// If depth mode is MinusOneToOne:
|
||||
// TranslateZ = (Near + Far) / 2
|
||||
// ScaleZ = (Far - Near) / 2
|
||||
// DepthNear/Far are sorted such as that Near is always less than Far.
|
||||
depthMode = extents.DepthNear != transform.TranslateZ &&
|
||||
extents.DepthFar != transform.TranslateZ
|
||||
? DepthMode.MinusOneToOne
|
||||
: DepthMode.ZeroToOne;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we can't guess from the viewport transform, then just use the depth mode register.
|
||||
depthMode = (DepthMode)(_state.State.DepthMode & 1);
|
||||
}
|
||||
|
||||
_context.Renderer.Pipeline.SetDepthMode(depthMode);
|
||||
_context.Renderer.Pipeline.SetDepthMode(GetDepthMode());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -719,6 +714,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
enables |= (depthBias.LineEnable ? PolygonModeMask.Line : 0);
|
||||
enables |= (depthBias.FillEnable ? PolygonModeMask.Fill : 0);
|
||||
|
||||
_pipeline.BiasEnable = enables;
|
||||
_context.Renderer.Pipeline.SetDepthBias(enables, factor, units / 2f, clamp);
|
||||
}
|
||||
|
||||
@ -760,7 +756,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
backMask = test.FrontMask;
|
||||
}
|
||||
|
||||
_context.Renderer.Pipeline.SetStencilTest(new StencilTestDescriptor(
|
||||
StencilTestDescriptor descriptor = new StencilTestDescriptor(
|
||||
test.Enable,
|
||||
test.FrontFunc,
|
||||
test.FrontSFail,
|
||||
@ -775,7 +771,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
backDpFail,
|
||||
backFuncRef,
|
||||
backFuncMask,
|
||||
backMask));
|
||||
backMask);
|
||||
|
||||
_pipeline.StencilTest = descriptor;
|
||||
_context.Renderer.Pipeline.SetStencilTest(descriptor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -844,6 +843,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
format);
|
||||
}
|
||||
|
||||
_pipeline.SetVertexAttribs(vertexAttribs);
|
||||
_context.Renderer.Pipeline.SetVertexAttribs(vertexAttribs);
|
||||
}
|
||||
|
||||
@ -855,6 +855,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
float width = _state.State.LineWidthSmooth;
|
||||
bool smooth = _state.State.LineSmoothEnable;
|
||||
|
||||
_pipeline.LineWidth = width;
|
||||
_context.Renderer.Pipeline.SetLineParameters(width, smooth);
|
||||
}
|
||||
|
||||
@ -881,6 +882,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
PrimitiveRestartState primitiveRestart = _state.State.PrimitiveRestartState;
|
||||
bool enable = primitiveRestart.Enable && (_drawState.DrawIndexed || _state.State.PrimitiveRestartDrawArrays);
|
||||
|
||||
_pipeline.PrimitiveRestartEnable = enable;
|
||||
_context.Renderer.Pipeline.SetPrimitiveRestart(enable, primitiveRestart.Index);
|
||||
}
|
||||
|
||||
@ -927,6 +929,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
if (!vertexBuffer.UnpackEnable())
|
||||
{
|
||||
_pipeline.VertexBuffers[index] = new BufferPipelineDescriptor(false, 0, 0);
|
||||
_channel.BufferManager.SetVertexBuffer(index, 0, 0, 0, 0);
|
||||
|
||||
continue;
|
||||
@ -944,6 +947,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
_drawState.IsAnyVbInstanced |= divisor != 0;
|
||||
|
||||
ulong vbSize = endAddress.Pack() - address + 1;
|
||||
ulong size;
|
||||
|
||||
if (_drawState.IbStreamer.HasInlineIndexData || _drawState.DrawIndexed || stride == 0 || instanced)
|
||||
@ -951,7 +955,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
// This size may be (much) larger than the real vertex buffer size.
|
||||
// Avoid calculating it this way, unless we don't have any other option.
|
||||
|
||||
size = endAddress.Pack() - address + 1;
|
||||
size = vbSize;
|
||||
|
||||
if (stride > 0 && indexTypeSmall && _drawState.DrawIndexed && !instanced)
|
||||
{
|
||||
@ -975,9 +979,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
var drawState = _state.State.VertexBufferDrawState;
|
||||
|
||||
size = (ulong)((firstInstance + drawState.First + drawState.Count) * stride);
|
||||
size = Math.Min(vbSize, (ulong)((firstInstance + drawState.First + drawState.Count) * stride));
|
||||
}
|
||||
|
||||
_pipeline.VertexBuffers[index] = new BufferPipelineDescriptor(_channel.MemoryManager.IsMapped(address), stride, divisor);
|
||||
_channel.BufferManager.SetVertexBuffer(index, address, size, stride, divisor);
|
||||
}
|
||||
}
|
||||
@ -990,6 +995,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
var yControl = _state.State.YControl;
|
||||
var face = _state.State.FaceState;
|
||||
|
||||
_pipeline.CullEnable = face.CullEnable;
|
||||
_pipeline.CullMode = face.CullFace;
|
||||
_context.Renderer.Pipeline.SetFaceCulling(face.CullEnable, face.CullFace);
|
||||
|
||||
UpdateFrontFace(yControl, face.FrontFace);
|
||||
@ -1009,6 +1016,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
frontFace = frontFace == FrontFace.CounterClockwise ? FrontFace.Clockwise : FrontFace.CounterClockwise;
|
||||
}
|
||||
|
||||
_pipeline.FrontFace = frontFace;
|
||||
_context.Renderer.Pipeline.SetFrontFace(frontFace);
|
||||
}
|
||||
|
||||
@ -1034,6 +1042,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
componentMask |= (colorMask.UnpackAlpha() ? 8u : 0u);
|
||||
|
||||
componentMasks[index] = componentMask;
|
||||
_pipeline.ColorWriteMask[index] = componentMask;
|
||||
}
|
||||
|
||||
_context.Renderer.Pipeline.SetRenderTargetColorMasks(componentMasks);
|
||||
@ -1082,6 +1091,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
blend.AlphaDstFactor);
|
||||
}
|
||||
|
||||
_pipeline.BlendDescriptors[index] = descriptor;
|
||||
_context.Renderer.Pipeline.SetBlendState(index, descriptor);
|
||||
}
|
||||
}
|
||||
@ -1093,6 +1103,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
LogicalOpState logicOpState = _state.State.LogicOpState;
|
||||
|
||||
_pipeline.SetLogicOpState(logicOpState.Enable, logicOpState.LogicalOp);
|
||||
_context.Renderer.Pipeline.SetLogicOpState(logicOpState.Enable, logicOpState.LogicalOp);
|
||||
}
|
||||
|
||||
@ -1138,7 +1149,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
GpuChannelPoolState poolState = GetPoolState();
|
||||
GpuChannelGraphicsState graphicsState = GetGraphicsState();
|
||||
|
||||
CachedShaderProgram gs = shaderCache.GetGraphicsShader(ref _state.State, _channel, poolState, graphicsState, addresses);
|
||||
CachedShaderProgram gs = shaderCache.GetGraphicsShader(ref _state.State, ref _pipeline, _channel, poolState, graphicsState, addresses);
|
||||
|
||||
_shaderSpecState = gs.SpecializationState;
|
||||
|
||||
@ -1245,13 +1256,69 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// <returns>Current GPU channel state</returns>
|
||||
private GpuChannelGraphicsState GetGraphicsState()
|
||||
{
|
||||
ref var vertexAttribState = ref _state.State.VertexAttribState;
|
||||
|
||||
Array32<AttributeType> attributeTypes = new Array32<AttributeType>();
|
||||
|
||||
for (int location = 0; location < attributeTypes.Length; location++)
|
||||
{
|
||||
attributeTypes[location] = vertexAttribState[location].UnpackType() switch
|
||||
{
|
||||
3 => AttributeType.Sint,
|
||||
4 => AttributeType.Uint,
|
||||
_ => AttributeType.Float
|
||||
};
|
||||
}
|
||||
|
||||
return new GpuChannelGraphicsState(
|
||||
_state.State.EarlyZForce,
|
||||
_drawState.Topology,
|
||||
_state.State.TessMode,
|
||||
_state.State.ViewportTransformEnable == 0,
|
||||
(_state.State.MultisampleControl & 1) != 0,
|
||||
_state.State.AlphaToCoverageDitherEnable);
|
||||
_state.State.AlphaToCoverageDitherEnable,
|
||||
_state.State.ViewportTransformEnable == 0,
|
||||
GetDepthMode() == DepthMode.MinusOneToOne,
|
||||
_state.State.VertexProgramPointSize,
|
||||
_state.State.PointSize,
|
||||
_state.State.AlphaTestEnable,
|
||||
_state.State.AlphaTestFunc,
|
||||
_state.State.AlphaTestRef,
|
||||
ref attributeTypes);
|
||||
}
|
||||
|
||||
private DepthMode GetDepthMode()
|
||||
{
|
||||
ref var transform = ref _state.State.ViewportTransform[0];
|
||||
ref var extents = ref _state.State.ViewportExtents[0];
|
||||
|
||||
DepthMode depthMode;
|
||||
|
||||
if (!float.IsInfinity(extents.DepthNear) &&
|
||||
!float.IsInfinity(extents.DepthFar) &&
|
||||
(extents.DepthFar - extents.DepthNear) != 0)
|
||||
{
|
||||
// Try to guess the depth mode being used on the high level API
|
||||
// based on current transform.
|
||||
// It is setup like so by said APIs:
|
||||
// If depth mode is ZeroToOne:
|
||||
// TranslateZ = Near
|
||||
// ScaleZ = Far - Near
|
||||
// If depth mode is MinusOneToOne:
|
||||
// TranslateZ = (Near + Far) / 2
|
||||
// ScaleZ = (Far - Near) / 2
|
||||
// DepthNear/Far are sorted such as that Near is always less than Far.
|
||||
depthMode = extents.DepthNear != transform.TranslateZ &&
|
||||
extents.DepthFar != transform.TranslateZ
|
||||
? DepthMode.MinusOneToOne
|
||||
: DepthMode.ZeroToOne;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we can't guess from the viewport transform, then just use the depth mode register.
|
||||
depthMode = (DepthMode)(_state.State.DepthMode & 1);
|
||||
}
|
||||
|
||||
return depthMode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,5 +1,6 @@
|
||||
using Ryujinx.Graphics.Device;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
|
||||
using Ryujinx.Graphics.Gpu.Engine.InlineToMemory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@ -13,6 +14,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
class ThreedClass : IDeviceState
|
||||
{
|
||||
private readonly GpuContext _context;
|
||||
private readonly GPFifoClass _fifoClass;
|
||||
private readonly DeviceStateWithShadow<ThreedClassState> _state;
|
||||
|
||||
private readonly InlineToMemoryClass _i2mClass;
|
||||
@ -26,9 +28,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="channel">GPU channel</param>
|
||||
public ThreedClass(GpuContext context, GpuChannel channel)
|
||||
public ThreedClass(GpuContext context, GpuChannel channel, GPFifoClass fifoClass)
|
||||
{
|
||||
_context = context;
|
||||
_fifoClass = fifoClass;
|
||||
_state = new DeviceStateWithShadow<ThreedClassState>(new Dictionary<string, RwCallback>
|
||||
{
|
||||
{ nameof(ThreedClassState.LaunchDma), new RwCallback(LaunchDma, null) },
|
||||
@ -114,6 +117,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// </summary>
|
||||
public void UpdateState()
|
||||
{
|
||||
_fifoClass.CreatePendingSyncs();
|
||||
_cbUpdater.FlushUboDirty();
|
||||
_stateUpdater.Update();
|
||||
}
|
||||
@ -172,6 +176,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
_stateUpdater.ForceShaderUpdate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create any syncs from WaitForIdle command that are currently pending.
|
||||
/// </summary>
|
||||
public void CreatePendingSyncs()
|
||||
{
|
||||
_fifoClass.CreatePendingSyncs();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flushes any queued UBO updates.
|
||||
/// </summary>
|
||||
|
@ -311,6 +311,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
return Attribute & 0x3fe00000;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the Maxwell attribute component type.
|
||||
/// </summary>
|
||||
/// <returns>Attribute component type</returns>
|
||||
public uint UnpackType()
|
||||
{
|
||||
return (Attribute >> 27) & 7;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -759,8 +768,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
public fixed uint Reserved10B0[18];
|
||||
public uint ClearFlags;
|
||||
public fixed uint Reserved10FC[25];
|
||||
public Array16<VertexAttribState> VertexAttribState;
|
||||
public fixed uint Reserved11A0[31];
|
||||
public Array32<VertexAttribState> VertexAttribState;
|
||||
public fixed uint Reserved11E0[15];
|
||||
public RtControl RtControl;
|
||||
public fixed uint Reserved1220[2];
|
||||
public Size3D RtDepthStencilSize;
|
||||
|
@ -30,8 +30,8 @@ namespace Ryujinx.Graphics.Gpu
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables fast 2d engine texture copies entirely on CPU when possible.
|
||||
/// Reduces stuttering and # of textures in games that copy textures around for streaming,
|
||||
/// as textures will not need to be created for the copy, and the data does not need to be
|
||||
/// Reduces stuttering and # of textures in games that copy textures around for streaming,
|
||||
/// as textures will not need to be created for the copy, and the data does not need to be
|
||||
/// flushed from GPU.
|
||||
/// </summary>
|
||||
public static bool Fast2DCopy = true;
|
||||
@ -56,5 +56,15 @@ namespace Ryujinx.Graphics.Gpu
|
||||
/// Enables or disables the shader cache.
|
||||
/// </summary>
|
||||
public static bool EnableShaderCache;
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables shader SPIR-V compilation.
|
||||
/// </summary>
|
||||
public static bool EnableSpirvCompilationOnVulkan = true;
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables recompression of compressed textures that are not natively supported by the host.
|
||||
/// </summary>
|
||||
public static bool EnableTextureRecompression = false;
|
||||
}
|
||||
}
|
@ -826,20 +826,25 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
depth,
|
||||
levels,
|
||||
layers,
|
||||
out Span<byte> decoded))
|
||||
out byte[] decoded))
|
||||
{
|
||||
string texInfo = $"{Info.Target} {Info.FormatInfo.Format} {Info.Width}x{Info.Height}x{Info.DepthOrLayers} levels {Info.Levels}";
|
||||
|
||||
Logger.Debug?.Print(LogClass.Gpu, $"Invalid ASTC texture at 0x{Info.GpuAddress:X} ({texInfo}).");
|
||||
}
|
||||
|
||||
if (GraphicsConfig.EnableTextureRecompression)
|
||||
{
|
||||
decoded = BCnEncoder.EncodeBC7(decoded, width, height, depth, levels, layers);
|
||||
}
|
||||
|
||||
data = decoded;
|
||||
}
|
||||
else if (!_context.Capabilities.SupportsR4G4Format && Format == Format.R4G4Unorm)
|
||||
{
|
||||
data = PixelConverter.ConvertR4G4ToR4G4B4A4(data);
|
||||
}
|
||||
else if (!_context.Capabilities.Supports3DTextureCompression && Target == Target.Texture3D)
|
||||
else if (!TextureCompatibility.HostSupportsBcFormat(Format, Target, _context.Capabilities))
|
||||
{
|
||||
switch (Format)
|
||||
{
|
||||
@ -863,6 +868,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
case Format.Bc5Unorm:
|
||||
data = BCnDecoder.DecodeBC5(data, width, height, depth, levels, layers, Format == Format.Bc5Snorm);
|
||||
break;
|
||||
case Format.Bc6HSfloat:
|
||||
case Format.Bc6HUfloat:
|
||||
data = BCnDecoder.DecodeBC6(data, width, height, depth, levels, layers, Format == Format.Bc6HSfloat);
|
||||
break;
|
||||
case Format.Bc7Srgb:
|
||||
case Format.Bc7Unorm:
|
||||
data = BCnDecoder.DecodeBC7(data, width, height, depth, levels, layers);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1151,7 +1164,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewFormatCompatible(Info, info, caps));
|
||||
if (result != TextureViewCompatibility.Incompatible)
|
||||
{
|
||||
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewTargetCompatible(Info, info));
|
||||
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewTargetCompatible(Info, info, ref caps));
|
||||
|
||||
bool bothMs = Info.Target.IsMultisample() && info.Target.IsMultisample();
|
||||
if (bothMs && (Info.SamplesInX != info.SamplesInX || Info.SamplesInY != info.SamplesInY))
|
||||
@ -1216,16 +1229,18 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
if (_arrayViewTexture == null && IsSameDimensionsTarget(target))
|
||||
{
|
||||
FormatInfo formatInfo = TextureCompatibility.ToHostCompatibleFormat(Info, _context.Capabilities);
|
||||
|
||||
TextureCreateInfo createInfo = new TextureCreateInfo(
|
||||
Info.Width,
|
||||
Info.Height,
|
||||
target == Target.CubemapArray ? 6 : 1,
|
||||
Info.Levels,
|
||||
Info.Samples,
|
||||
Info.FormatInfo.BlockWidth,
|
||||
Info.FormatInfo.BlockHeight,
|
||||
Info.FormatInfo.BytesPerPixel,
|
||||
Info.FormatInfo.Format,
|
||||
formatInfo.BlockWidth,
|
||||
formatInfo.BlockHeight,
|
||||
formatInfo.BytesPerPixel,
|
||||
formatInfo.Format,
|
||||
Info.DepthStencilMode,
|
||||
target,
|
||||
Info.SwizzleR,
|
||||
|
@ -280,6 +280,30 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return changed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the vertex stage requires a scale value.
|
||||
/// </summary>
|
||||
private bool VertexRequiresScale()
|
||||
{
|
||||
for (int i = 0; i < _textureBindingsCount[0]; i++)
|
||||
{
|
||||
if ((_textureBindings[0][i].Flags & TextureUsageFlags.NeedsScaleValue) != 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < _imageBindingsCount[0]; i++)
|
||||
{
|
||||
if ((_imageBindings[0][i].Flags & TextureUsageFlags.NeedsScaleValue) != 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uploads texture and image scales to the backend when they are used.
|
||||
/// </summary>
|
||||
@ -291,10 +315,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
int fragmentIndex = (int)ShaderStage.Fragment - 1;
|
||||
int fragmentTotal = _isCompute ? 0 : (_textureBindingsCount[fragmentIndex] + _imageBindingsCount[fragmentIndex]);
|
||||
|
||||
if (total != 0 && fragmentTotal != _lastFragmentTotal)
|
||||
if (total != 0 && fragmentTotal != _lastFragmentTotal && VertexRequiresScale())
|
||||
{
|
||||
// Must update scales in the support buffer if:
|
||||
// - Vertex stage has bindings.
|
||||
// - Vertex stage has bindings that require scale.
|
||||
// - Fragment stage binding count has been updated since last render scale update.
|
||||
|
||||
_scaleChanged = true;
|
||||
@ -420,6 +444,25 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Counts the total number of texture bindings used by all shader stages.
|
||||
/// </summary>
|
||||
/// <returns>The total amount of textures used</returns>
|
||||
private int GetTextureBindingsCount()
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
for (int i = 0; i < _textureBindings.Length; i++)
|
||||
{
|
||||
if (_textureBindings[i] != null)
|
||||
{
|
||||
count += _textureBindings[i].Length;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the texture bindings are visible to the host GPU.
|
||||
/// Note: this actually performs the binding using the host graphics API.
|
||||
@ -501,7 +544,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
state.ScaleIndex = index;
|
||||
state.UsageFlags = usageFlags;
|
||||
|
||||
_context.Renderer.Pipeline.SetTexture(bindingInfo.Binding, hostTextureRebind);
|
||||
_context.Renderer.Pipeline.SetTextureAndSampler(stage, bindingInfo.Binding, hostTextureRebind, state.Sampler);
|
||||
}
|
||||
|
||||
continue;
|
||||
@ -514,44 +557,42 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
specStateMatches &= specState.MatchesTexture(stage, index, descriptor);
|
||||
|
||||
Sampler sampler = _samplerPool?.Get(samplerId);
|
||||
|
||||
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
||||
ISampler hostSampler = sampler?.GetHostSampler(texture);
|
||||
|
||||
if (hostTexture != null && texture.Target == Target.TextureBuffer)
|
||||
{
|
||||
// Ensure that the buffer texture is using the correct buffer as storage.
|
||||
// Buffers are frequently re-created to accomodate larger data, so we need to re-bind
|
||||
// to ensure we're not using a old buffer that was already deleted.
|
||||
_channel.BufferManager.SetBufferTextureStorage(hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, bindingInfo.Format, false);
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, bindingInfo.Format, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (state.Texture != hostTexture)
|
||||
{
|
||||
if (UpdateScale(texture, usageFlags, index, stage))
|
||||
{
|
||||
hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
||||
}
|
||||
bool textureOrSamplerChanged = state.Texture != hostTexture || state.Sampler != hostSampler;
|
||||
|
||||
if ((state.ScaleIndex != index || state.UsageFlags != usageFlags || textureOrSamplerChanged) &&
|
||||
UpdateScale(texture, usageFlags, index, stage))
|
||||
{
|
||||
hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
||||
textureOrSamplerChanged = true;
|
||||
}
|
||||
|
||||
if (textureOrSamplerChanged)
|
||||
{
|
||||
state.Texture = hostTexture;
|
||||
state.ScaleIndex = index;
|
||||
state.UsageFlags = usageFlags;
|
||||
|
||||
_context.Renderer.Pipeline.SetTexture(bindingInfo.Binding, hostTexture);
|
||||
}
|
||||
|
||||
Sampler sampler = samplerPool?.Get(samplerId);
|
||||
state.CachedSampler = sampler;
|
||||
|
||||
ISampler hostSampler = sampler?.GetHostSampler(texture);
|
||||
|
||||
if (state.Sampler != hostSampler)
|
||||
{
|
||||
state.Sampler = hostSampler;
|
||||
|
||||
_context.Renderer.Pipeline.SetSampler(bindingInfo.Binding, hostSampler);
|
||||
_context.Renderer.Pipeline.SetTextureAndSampler(stage, bindingInfo.Binding, hostTexture, hostSampler);
|
||||
}
|
||||
|
||||
state.CachedTexture = texture;
|
||||
state.CachedSampler = sampler;
|
||||
state.InvalidatedSequence = texture?.InvalidatedSequence ?? 0;
|
||||
}
|
||||
}
|
||||
@ -625,7 +666,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
cachedTexture?.SignalModified();
|
||||
}
|
||||
|
||||
if ((state.ScaleIndex != index || state.UsageFlags != usageFlags) &&
|
||||
if ((state.ScaleIndex != scaleIndex || state.UsageFlags != usageFlags) &&
|
||||
UpdateScale(state.CachedTexture, usageFlags, scaleIndex, stage))
|
||||
{
|
||||
ITexture hostTextureRebind = state.CachedTexture.GetTargetTexture(bindingInfo.Target);
|
||||
@ -663,7 +704,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
format = texture.Format;
|
||||
}
|
||||
|
||||
_channel.BufferManager.SetBufferTextureStorage(hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, format, true);
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, format, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -672,13 +713,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
texture?.SignalModified();
|
||||
}
|
||||
|
||||
if ((state.ScaleIndex != scaleIndex || state.UsageFlags != usageFlags || state.Texture != hostTexture) &&
|
||||
UpdateScale(texture, usageFlags, scaleIndex, stage))
|
||||
{
|
||||
hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
||||
}
|
||||
|
||||
if (state.Texture != hostTexture)
|
||||
{
|
||||
if (UpdateScale(texture, usageFlags, scaleIndex, stage))
|
||||
{
|
||||
hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
||||
}
|
||||
|
||||
state.Texture = hostTexture;
|
||||
state.ScaleIndex = scaleIndex;
|
||||
state.UsageFlags = usageFlags;
|
||||
|
@ -71,11 +71,15 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
if (info.FormatInfo.Format.IsAstcUnorm())
|
||||
{
|
||||
return new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4);
|
||||
return GraphicsConfig.EnableTextureRecompression
|
||||
? new FormatInfo(Format.Bc7Unorm, 4, 4, 16, 4)
|
||||
: new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4);
|
||||
}
|
||||
else if (info.FormatInfo.Format.IsAstcSrgb())
|
||||
{
|
||||
return new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4);
|
||||
return GraphicsConfig.EnableTextureRecompression
|
||||
? new FormatInfo(Format.Bc7Srgb, 4, 4, 16, 4)
|
||||
: new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4);
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,9 +88,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return new FormatInfo(Format.R4G4B4A4Unorm, 1, 1, 2, 4);
|
||||
}
|
||||
|
||||
if (!caps.Supports3DTextureCompression && info.Target == Target.Texture3D)
|
||||
if (!HostSupportsBcFormat(info.FormatInfo.Format, info.Target, caps))
|
||||
{
|
||||
// The host API does not support 3D compressed formats.
|
||||
// The host API does not this compressed format.
|
||||
// We assume software decompression will be done for those textures,
|
||||
// and so we adjust the format here to match the decompressor output.
|
||||
switch (info.FormatInfo.Format)
|
||||
@ -94,10 +98,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
case Format.Bc1RgbaSrgb:
|
||||
case Format.Bc2Srgb:
|
||||
case Format.Bc3Srgb:
|
||||
case Format.Bc7Srgb:
|
||||
return new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4);
|
||||
case Format.Bc1RgbaUnorm:
|
||||
case Format.Bc2Unorm:
|
||||
case Format.Bc3Unorm:
|
||||
case Format.Bc7Unorm:
|
||||
return new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4);
|
||||
case Format.Bc4Unorm:
|
||||
return new FormatInfo(Format.R8Unorm, 1, 1, 1, 1);
|
||||
@ -107,12 +113,50 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return new FormatInfo(Format.R8G8Unorm, 1, 1, 2, 2);
|
||||
case Format.Bc5Snorm:
|
||||
return new FormatInfo(Format.R8G8Snorm, 1, 1, 2, 2);
|
||||
case Format.Bc6HSfloat:
|
||||
case Format.Bc6HUfloat:
|
||||
return new FormatInfo(Format.R16G16B16A16Float, 1, 1, 8, 4);
|
||||
}
|
||||
}
|
||||
|
||||
return info.FormatInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the host API supports a given texture compression format of the BC family.
|
||||
/// </summary>
|
||||
/// <param name="format">BC format to be checked</param>
|
||||
/// <param name="target">Target usage of the texture</param>
|
||||
/// <param name="caps">Host GPU Capabilities</param>
|
||||
/// <returns>True if the texture host supports the format with the given target usage, false otherwise</returns>
|
||||
public static bool HostSupportsBcFormat(Format format, Target target, Capabilities caps)
|
||||
{
|
||||
bool not3DOr3DCompressionSupported = target != Target.Texture3D || caps.Supports3DTextureCompression;
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case Format.Bc1RgbaSrgb:
|
||||
case Format.Bc1RgbaUnorm:
|
||||
case Format.Bc2Srgb:
|
||||
case Format.Bc2Unorm:
|
||||
case Format.Bc3Srgb:
|
||||
case Format.Bc3Unorm:
|
||||
return caps.SupportsBc123Compression && not3DOr3DCompressionSupported;
|
||||
case Format.Bc4Unorm:
|
||||
case Format.Bc4Snorm:
|
||||
case Format.Bc5Unorm:
|
||||
case Format.Bc5Snorm:
|
||||
return caps.SupportsBc45Compression && not3DOr3DCompressionSupported;
|
||||
case Format.Bc6HSfloat:
|
||||
case Format.Bc6HUfloat:
|
||||
case Format.Bc7Srgb:
|
||||
case Format.Bc7Unorm:
|
||||
return caps.SupportsBc67Compression && not3DOr3DCompressionSupported;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a texture can flush its data back to guest memory.
|
||||
/// </summary>
|
||||
@ -627,9 +671,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// </summary>
|
||||
/// <param name="lhs">Texture information of the texture view</param
|
||||
/// <param name="rhs">Texture information of the texture view</param>
|
||||
/// <param name="isCopy">True to check for copy rather than view compatibility</param>
|
||||
/// <param name="caps">Host GPU capabilities</param>
|
||||
/// <returns>True if the targets are compatible, false otherwise</returns>
|
||||
public static TextureViewCompatibility ViewTargetCompatible(TextureInfo lhs, TextureInfo rhs)
|
||||
public static TextureViewCompatibility ViewTargetCompatible(TextureInfo lhs, TextureInfo rhs, ref Capabilities caps)
|
||||
{
|
||||
bool result = false;
|
||||
switch (lhs.Target)
|
||||
@ -646,14 +690,24 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
break;
|
||||
|
||||
case Target.Texture2DArray:
|
||||
result = rhs.Target == Target.Texture2D ||
|
||||
rhs.Target == Target.Texture2DArray;
|
||||
|
||||
if (rhs.Target == Target.Cubemap || rhs.Target == Target.CubemapArray)
|
||||
{
|
||||
return caps.SupportsCubemapView ? TextureViewCompatibility.Full : TextureViewCompatibility.CopyOnly;
|
||||
}
|
||||
break;
|
||||
case Target.Cubemap:
|
||||
case Target.CubemapArray:
|
||||
result = rhs.Target == Target.Texture2D ||
|
||||
rhs.Target == Target.Texture2DArray ||
|
||||
rhs.Target == Target.Cubemap ||
|
||||
result = rhs.Target == Target.Cubemap ||
|
||||
rhs.Target == Target.CubemapArray;
|
||||
break;
|
||||
|
||||
if (rhs.Target == Target.Texture2D || rhs.Target == Target.Texture2DArray)
|
||||
{
|
||||
return caps.SupportsCubemapView ? TextureViewCompatibility.Full : TextureViewCompatibility.CopyOnly;
|
||||
}
|
||||
break;
|
||||
case Target.Texture2DMultisample:
|
||||
case Target.Texture2DMultisampleArray:
|
||||
if (rhs.Target == Target.Texture2D || rhs.Target == Target.Texture2DArray)
|
||||
@ -744,7 +798,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <returns>True if the texture target and samples count matches, false otherwise</returns>
|
||||
public static bool TargetAndSamplesCompatible(TextureInfo lhs, TextureInfo rhs)
|
||||
{
|
||||
return lhs.Target == rhs.Target &&
|
||||
return lhs.Target == rhs.Target &&
|
||||
lhs.SamplesInX == rhs.SamplesInX &&
|
||||
lhs.SamplesInY == rhs.SamplesInY;
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
|
||||
|
@ -435,7 +435,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
}
|
||||
else
|
||||
{
|
||||
_context.Renderer.Pipeline.SetTexture(binding.BindingInfo.Binding, binding.Texture);
|
||||
_context.Renderer.Pipeline.SetTextureAndSampler(binding.Stage, binding.BindingInfo.Binding, binding.Texture, null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -719,17 +719,25 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <summary>
|
||||
/// Sets the buffer storage of a buffer texture. This will be bound when the buffer manager commits bindings.
|
||||
/// </summary>
|
||||
/// <param name="stage">Shader stage accessing the texture</param>
|
||||
/// <param name="texture">Buffer texture</param>
|
||||
/// <param name="address">Address of the buffer in memory</param>
|
||||
/// <param name="size">Size of the buffer in bytes</param>
|
||||
/// <param name="bindingInfo">Binding info for the buffer texture</param>
|
||||
/// <param name="format">Format of the buffer texture</param>
|
||||
/// <param name="isImage">Whether the binding is for an image or a sampler</param>
|
||||
public void SetBufferTextureStorage(ITexture texture, ulong address, ulong size, TextureBindingInfo bindingInfo, Format format, bool isImage)
|
||||
public void SetBufferTextureStorage(
|
||||
ShaderStage stage,
|
||||
ITexture texture,
|
||||
ulong address,
|
||||
ulong size,
|
||||
TextureBindingInfo bindingInfo,
|
||||
Format format,
|
||||
bool isImage)
|
||||
{
|
||||
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(address, size);
|
||||
|
||||
_bufferTextures.Add(new BufferTextureBinding(texture, address, size, bindingInfo, format, isImage));
|
||||
_bufferTextures.Add(new BufferTextureBinding(stage, texture, address, size, bindingInfo, format, isImage));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,5 +1,6 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
@ -8,6 +9,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </summary>
|
||||
struct BufferTextureBinding
|
||||
{
|
||||
/// <summary>
|
||||
/// Shader stage accessing the texture.
|
||||
/// </summary>
|
||||
public ShaderStage Stage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The buffer texture.
|
||||
/// </summary>
|
||||
@ -41,14 +47,23 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <summary>
|
||||
/// Create a new buffer texture binding.
|
||||
/// </summary>
|
||||
/// <param name="stage">Shader stage accessing the texture</param>
|
||||
/// <param name="texture">Buffer texture</param>
|
||||
/// <param name="address">Base address</param>
|
||||
/// <param name="size">Size in bytes</param>
|
||||
/// <param name="bindingInfo">Binding info</param>
|
||||
/// <param name="format">Binding format</param>
|
||||
/// <param name="isImage">Whether the binding is for an image or a sampler</param>
|
||||
public BufferTextureBinding(ITexture texture, ulong address, ulong size, TextureBindingInfo bindingInfo, Format format, bool isImage)
|
||||
public BufferTextureBinding(
|
||||
ShaderStage stage,
|
||||
ITexture texture,
|
||||
ulong address,
|
||||
ulong size,
|
||||
TextureBindingInfo bindingInfo,
|
||||
Format format,
|
||||
bool isImage)
|
||||
{
|
||||
Stage = stage;
|
||||
Texture = texture;
|
||||
Address = address;
|
||||
Size = size;
|
||||
|
@ -14,8 +14,4 @@
|
||||
<ProjectReference Include="..\Ryujinx.Graphics.Shader\Ryujinx.Graphics.Shader.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SharpZipLib" Version="1.3.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -1,617 +0,0 @@
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||
{
|
||||
/// <summary>
|
||||
/// Represent a cache collection handling one shader cache.
|
||||
/// </summary>
|
||||
class CacheCollection : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Possible operation to do on the <see cref="_fileWriterWorkerQueue"/>.
|
||||
/// </summary>
|
||||
private enum CacheFileOperation
|
||||
{
|
||||
/// <summary>
|
||||
/// Save a new entry in the temp cache.
|
||||
/// </summary>
|
||||
SaveTempEntry,
|
||||
|
||||
/// <summary>
|
||||
/// Save the hash manifest.
|
||||
/// </summary>
|
||||
SaveManifest,
|
||||
|
||||
/// <summary>
|
||||
/// Remove entries from the hash manifest and save it.
|
||||
/// </summary>
|
||||
RemoveManifestEntries,
|
||||
|
||||
/// <summary>
|
||||
/// Remove entries from the hash manifest and save it, and also deletes the temporary file.
|
||||
/// </summary>
|
||||
RemoveManifestEntryAndTempFile,
|
||||
|
||||
/// <summary>
|
||||
/// Flush temporary cache to archive.
|
||||
/// </summary>
|
||||
FlushToArchive,
|
||||
|
||||
/// <summary>
|
||||
/// Signal when hitting this point. This is useful to know if all previous operations were performed.
|
||||
/// </summary>
|
||||
Synchronize
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represent an operation to perform on the <see cref="_fileWriterWorkerQueue"/>.
|
||||
/// </summary>
|
||||
private class CacheFileOperationTask
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of operation to perform.
|
||||
/// </summary>
|
||||
public CacheFileOperation Type;
|
||||
|
||||
/// <summary>
|
||||
/// The data associated to this operation or null.
|
||||
/// </summary>
|
||||
public object Data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data associated to the <see cref="CacheFileOperation.SaveTempEntry"/> operation.
|
||||
/// </summary>
|
||||
private class CacheFileSaveEntryTaskData
|
||||
{
|
||||
/// <summary>
|
||||
/// The key of the entry to cache.
|
||||
/// </summary>
|
||||
public Hash128 Key;
|
||||
|
||||
/// <summary>
|
||||
/// The value of the entry to cache.
|
||||
/// </summary>
|
||||
public byte[] Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The directory of the shader cache.
|
||||
/// </summary>
|
||||
private readonly string _cacheDirectory;
|
||||
|
||||
/// <summary>
|
||||
/// The version of the cache.
|
||||
/// </summary>
|
||||
private readonly ulong _version;
|
||||
|
||||
/// <summary>
|
||||
/// The hash type of the cache.
|
||||
/// </summary>
|
||||
private readonly CacheHashType _hashType;
|
||||
|
||||
/// <summary>
|
||||
/// The graphics API of the cache.
|
||||
/// </summary>
|
||||
private readonly CacheGraphicsApi _graphicsApi;
|
||||
|
||||
/// <summary>
|
||||
/// The table of all the hash registered in the cache.
|
||||
/// </summary>
|
||||
private HashSet<Hash128> _hashTable;
|
||||
|
||||
/// <summary>
|
||||
/// The queue of operations to be performed by the file writer worker.
|
||||
/// </summary>
|
||||
private AsyncWorkQueue<CacheFileOperationTask> _fileWriterWorkerQueue;
|
||||
|
||||
/// <summary>
|
||||
/// Main storage of the cache collection.
|
||||
/// </summary>
|
||||
private ZipFile _cacheArchive;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the cache collection supports modification.
|
||||
/// </summary>
|
||||
public bool IsReadOnly { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Immutable copy of the hash table.
|
||||
/// </summary>
|
||||
public ReadOnlySpan<Hash128> HashTable => _hashTable.ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// Get the temp path to the cache data directory.
|
||||
/// </summary>
|
||||
/// <returns>The temp path to the cache data directory</returns>
|
||||
private string GetCacheTempDataPath() => CacheHelper.GetCacheTempDataPath(_cacheDirectory);
|
||||
|
||||
/// <summary>
|
||||
/// The path to the cache archive file.
|
||||
/// </summary>
|
||||
/// <returns>The path to the cache archive file</returns>
|
||||
private string GetArchivePath() => CacheHelper.GetArchivePath(_cacheDirectory);
|
||||
|
||||
/// <summary>
|
||||
/// The path to the cache manifest file.
|
||||
/// </summary>
|
||||
/// <returns>The path to the cache manifest file</returns>
|
||||
private string GetManifestPath() => CacheHelper.GetManifestPath(_cacheDirectory);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new temp path to the given cached file via its hash.
|
||||
/// </summary>
|
||||
/// <param name="key">The hash of the cached data</param>
|
||||
/// <returns>New path to the given cached file</returns>
|
||||
private string GenCacheTempFilePath(Hash128 key) => CacheHelper.GenCacheTempFilePath(_cacheDirectory, key);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new cache collection.
|
||||
/// </summary>
|
||||
/// <param name="baseCacheDirectory">The directory of the shader cache</param>
|
||||
/// <param name="hashType">The hash type of the shader cache</param>
|
||||
/// <param name="graphicsApi">The graphics api of the shader cache</param>
|
||||
/// <param name="shaderProvider">The shader provider name of the shader cache</param>
|
||||
/// <param name="cacheName">The name of the cache</param>
|
||||
/// <param name="version">The version of the cache</param>
|
||||
public CacheCollection(string baseCacheDirectory, CacheHashType hashType, CacheGraphicsApi graphicsApi, string shaderProvider, string cacheName, ulong version)
|
||||
{
|
||||
if (hashType != CacheHashType.XxHash128)
|
||||
{
|
||||
throw new NotImplementedException($"{hashType}");
|
||||
}
|
||||
|
||||
_cacheDirectory = CacheHelper.GenerateCachePath(baseCacheDirectory, graphicsApi, shaderProvider, cacheName);
|
||||
_graphicsApi = graphicsApi;
|
||||
_hashType = hashType;
|
||||
_version = version;
|
||||
_hashTable = new HashSet<Hash128>();
|
||||
IsReadOnly = CacheHelper.IsArchiveReadOnly(GetArchivePath());
|
||||
|
||||
Load();
|
||||
|
||||
_fileWriterWorkerQueue = new AsyncWorkQueue<CacheFileOperationTask>(HandleCacheTask, $"CacheCollection.Worker.{cacheName}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load the cache manifest file and recreate it if invalid.
|
||||
/// </summary>
|
||||
private void Load()
|
||||
{
|
||||
bool isValid = false;
|
||||
|
||||
if (Directory.Exists(_cacheDirectory))
|
||||
{
|
||||
string manifestPath = GetManifestPath();
|
||||
|
||||
if (File.Exists(manifestPath))
|
||||
{
|
||||
Memory<byte> rawManifest = File.ReadAllBytes(manifestPath);
|
||||
|
||||
if (MemoryMarshal.TryRead(rawManifest.Span, out CacheManifestHeader manifestHeader))
|
||||
{
|
||||
Memory<byte> hashTableRaw = rawManifest.Slice(Unsafe.SizeOf<CacheManifestHeader>());
|
||||
|
||||
isValid = manifestHeader.IsValid(_graphicsApi, _hashType, hashTableRaw.Span) && _version == manifestHeader.Version;
|
||||
|
||||
if (isValid)
|
||||
{
|
||||
ReadOnlySpan<Hash128> hashTable = MemoryMarshal.Cast<byte, Hash128>(hashTableRaw.Span);
|
||||
|
||||
foreach (Hash128 hash in hashTable)
|
||||
{
|
||||
_hashTable.Add(hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isValid)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, $"Shader collection \"{_cacheDirectory}\" got invalidated, cache will need to be rebuilt.");
|
||||
|
||||
if (Directory.Exists(_cacheDirectory))
|
||||
{
|
||||
Directory.Delete(_cacheDirectory, true);
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(_cacheDirectory);
|
||||
|
||||
SaveManifest();
|
||||
}
|
||||
|
||||
FlushToArchive();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue a task to remove entries from the hash manifest.
|
||||
/// </summary>
|
||||
/// <param name="entries">Entries to remove from the manifest</param>
|
||||
public void RemoveManifestEntriesAsync(HashSet<Hash128> entries)
|
||||
{
|
||||
if (IsReadOnly)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, "Trying to remove manifest entries on a read-only cache, ignoring.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_fileWriterWorkerQueue.Add(new CacheFileOperationTask
|
||||
{
|
||||
Type = CacheFileOperation.RemoveManifestEntries,
|
||||
Data = entries
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove given entries from the manifest.
|
||||
/// </summary>
|
||||
/// <param name="entries">Entries to remove from the manifest</param>
|
||||
private void RemoveManifestEntries(HashSet<Hash128> entries)
|
||||
{
|
||||
lock (_hashTable)
|
||||
{
|
||||
foreach (Hash128 entry in entries)
|
||||
{
|
||||
_hashTable.Remove(entry);
|
||||
}
|
||||
|
||||
SaveManifest();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove given entry from the manifest and delete the temporary file.
|
||||
/// </summary>
|
||||
/// <param name="entry">Entry to remove from the manifest</param>
|
||||
private void RemoveManifestEntryAndTempFile(Hash128 entry)
|
||||
{
|
||||
lock (_hashTable)
|
||||
{
|
||||
_hashTable.Remove(entry);
|
||||
SaveManifest();
|
||||
}
|
||||
|
||||
File.Delete(GenCacheTempFilePath(entry));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue a task to flush temporary files to the archive on the worker.
|
||||
/// </summary>
|
||||
public void FlushToArchiveAsync()
|
||||
{
|
||||
_fileWriterWorkerQueue.Add(new CacheFileOperationTask
|
||||
{
|
||||
Type = CacheFileOperation.FlushToArchive
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait for all tasks before this given point to be done.
|
||||
/// </summary>
|
||||
public void Synchronize()
|
||||
{
|
||||
using (ManualResetEvent evnt = new ManualResetEvent(false))
|
||||
{
|
||||
_fileWriterWorkerQueue.Add(new CacheFileOperationTask
|
||||
{
|
||||
Type = CacheFileOperation.Synchronize,
|
||||
Data = evnt
|
||||
});
|
||||
|
||||
evnt.WaitOne();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flush temporary files to the archive.
|
||||
/// </summary>
|
||||
/// <remarks>This dispose <see cref="_cacheArchive"/> if not null and reinstantiate it.</remarks>
|
||||
private void FlushToArchive()
|
||||
{
|
||||
EnsureArchiveUpToDate();
|
||||
|
||||
// Open the zip in readonly to avoid anyone modifying/corrupting it during normal operations.
|
||||
_cacheArchive = new ZipFile(File.OpenRead(GetArchivePath()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save temporary files not in archive.
|
||||
/// </summary>
|
||||
/// <remarks>This dispose <see cref="_cacheArchive"/> if not null.</remarks>
|
||||
public void EnsureArchiveUpToDate()
|
||||
{
|
||||
// First close previous opened instance if found.
|
||||
if (_cacheArchive != null)
|
||||
{
|
||||
_cacheArchive.Close();
|
||||
}
|
||||
|
||||
string archivePath = GetArchivePath();
|
||||
|
||||
if (IsReadOnly)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, $"Cache collection archive in read-only, archiving task skipped.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (CacheHelper.IsArchiveReadOnly(archivePath))
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, $"Cache collection archive in use, archiving task skipped.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!File.Exists(archivePath))
|
||||
{
|
||||
using (ZipFile newZip = ZipFile.Create(archivePath))
|
||||
{
|
||||
// Workaround for SharpZipLib issue #395
|
||||
newZip.BeginUpdate();
|
||||
newZip.CommitUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
// Open the zip in read/write.
|
||||
_cacheArchive = new ZipFile(File.Open(archivePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None));
|
||||
|
||||
Logger.Info?.Print(LogClass.Gpu, $"Updating cache collection archive {archivePath}...");
|
||||
|
||||
// Update the content of the zip.
|
||||
lock (_hashTable)
|
||||
{
|
||||
CacheHelper.EnsureArchiveUpToDate(_cacheDirectory, _cacheArchive, _hashTable);
|
||||
|
||||
// Close the instance to force a flush.
|
||||
_cacheArchive.Close();
|
||||
_cacheArchive = null;
|
||||
|
||||
string cacheTempDataPath = GetCacheTempDataPath();
|
||||
|
||||
// Create the cache data path if missing.
|
||||
if (!Directory.Exists(cacheTempDataPath))
|
||||
{
|
||||
Directory.CreateDirectory(cacheTempDataPath);
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Info?.Print(LogClass.Gpu, $"Updated cache collection archive {archivePath}.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save the manifest file.
|
||||
/// </summary>
|
||||
private void SaveManifest()
|
||||
{
|
||||
byte[] data;
|
||||
|
||||
lock (_hashTable)
|
||||
{
|
||||
data = CacheHelper.ComputeManifest(_version, _graphicsApi, _hashType, _hashTable);
|
||||
}
|
||||
|
||||
File.WriteAllBytes(GetManifestPath(), data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a cached file with the given hash.
|
||||
/// </summary>
|
||||
/// <param name="keyHash">The given hash</param>
|
||||
/// <returns>The cached file if present or null</returns>
|
||||
public byte[] GetValueRaw(ref Hash128 keyHash)
|
||||
{
|
||||
return GetValueRawFromArchive(ref keyHash) ?? GetValueRawFromFile(ref keyHash);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a cached file with the given hash that is present in the archive.
|
||||
/// </summary>
|
||||
/// <param name="keyHash">The given hash</param>
|
||||
/// <returns>The cached file if present or null</returns>
|
||||
private byte[] GetValueRawFromArchive(ref Hash128 keyHash)
|
||||
{
|
||||
bool found;
|
||||
|
||||
lock (_hashTable)
|
||||
{
|
||||
found = _hashTable.Contains(keyHash);
|
||||
}
|
||||
|
||||
if (found)
|
||||
{
|
||||
return CacheHelper.ReadFromArchive(_cacheArchive, keyHash);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a cached file with the given hash that is not present in the archive.
|
||||
/// </summary>
|
||||
/// <param name="keyHash">The given hash</param>
|
||||
/// <returns>The cached file if present or null</returns>
|
||||
private byte[] GetValueRawFromFile(ref Hash128 keyHash)
|
||||
{
|
||||
bool found;
|
||||
|
||||
lock (_hashTable)
|
||||
{
|
||||
found = _hashTable.Contains(keyHash);
|
||||
}
|
||||
|
||||
if (found)
|
||||
{
|
||||
return CacheHelper.ReadFromFile(GetCacheTempDataPath(), keyHash);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void HandleCacheTask(CacheFileOperationTask task)
|
||||
{
|
||||
switch (task.Type)
|
||||
{
|
||||
case CacheFileOperation.SaveTempEntry:
|
||||
SaveTempEntry((CacheFileSaveEntryTaskData)task.Data);
|
||||
break;
|
||||
case CacheFileOperation.SaveManifest:
|
||||
SaveManifest();
|
||||
break;
|
||||
case CacheFileOperation.RemoveManifestEntries:
|
||||
RemoveManifestEntries((HashSet<Hash128>)task.Data);
|
||||
break;
|
||||
case CacheFileOperation.RemoveManifestEntryAndTempFile:
|
||||
RemoveManifestEntryAndTempFile((Hash128)task.Data);
|
||||
break;
|
||||
case CacheFileOperation.FlushToArchive:
|
||||
FlushToArchive();
|
||||
break;
|
||||
case CacheFileOperation.Synchronize:
|
||||
((ManualResetEvent)task.Data).Set();
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException($"{task.Type}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save a new entry in the temp cache.
|
||||
/// </summary>
|
||||
/// <param name="entry">The entry to save in the temp cache</param>
|
||||
private void SaveTempEntry(CacheFileSaveEntryTaskData entry)
|
||||
{
|
||||
string tempPath = GenCacheTempFilePath(entry.Key);
|
||||
|
||||
File.WriteAllBytes(tempPath, entry.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a new value in the cache with a given hash.
|
||||
/// </summary>
|
||||
/// <param name="keyHash">The hash to use for the value in the cache</param>
|
||||
/// <param name="value">The value to cache</param>
|
||||
public void AddValue(ref Hash128 keyHash, byte[] value)
|
||||
{
|
||||
if (IsReadOnly)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, $"Trying to add {keyHash} on a read-only cache, ignoring.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Assert(value != null);
|
||||
|
||||
bool isAlreadyPresent;
|
||||
|
||||
lock (_hashTable)
|
||||
{
|
||||
isAlreadyPresent = !_hashTable.Add(keyHash);
|
||||
}
|
||||
|
||||
if (isAlreadyPresent)
|
||||
{
|
||||
// NOTE: Used for debug
|
||||
File.WriteAllBytes(GenCacheTempFilePath(new Hash128()), value);
|
||||
|
||||
throw new InvalidOperationException($"Cache collision found on {GenCacheTempFilePath(keyHash)}");
|
||||
}
|
||||
|
||||
// Queue file change operations
|
||||
_fileWriterWorkerQueue.Add(new CacheFileOperationTask
|
||||
{
|
||||
Type = CacheFileOperation.SaveTempEntry,
|
||||
Data = new CacheFileSaveEntryTaskData
|
||||
{
|
||||
Key = keyHash,
|
||||
Value = value
|
||||
}
|
||||
});
|
||||
|
||||
// Save the manifest changes
|
||||
_fileWriterWorkerQueue.Add(new CacheFileOperationTask
|
||||
{
|
||||
Type = CacheFileOperation.SaveManifest,
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace a value at the given hash in the cache.
|
||||
/// </summary>
|
||||
/// <param name="keyHash">The hash to use for the value in the cache</param>
|
||||
/// <param name="value">The value to cache</param>
|
||||
public void ReplaceValue(ref Hash128 keyHash, byte[] value)
|
||||
{
|
||||
if (IsReadOnly)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, $"Trying to replace {keyHash} on a read-only cache, ignoring.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Assert(value != null);
|
||||
|
||||
// Only queue file change operations
|
||||
_fileWriterWorkerQueue.Add(new CacheFileOperationTask
|
||||
{
|
||||
Type = CacheFileOperation.SaveTempEntry,
|
||||
Data = new CacheFileSaveEntryTaskData
|
||||
{
|
||||
Key = keyHash,
|
||||
Value = value
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a value at the given hash from the cache.
|
||||
/// </summary>
|
||||
/// <param name="keyHash">The hash of the value in the cache</param>
|
||||
public void RemoveValue(ref Hash128 keyHash)
|
||||
{
|
||||
if (IsReadOnly)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, $"Trying to remove {keyHash} on a read-only cache, ignoring.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Only queue file change operations
|
||||
_fileWriterWorkerQueue.Add(new CacheFileOperationTask
|
||||
{
|
||||
Type = CacheFileOperation.RemoveManifestEntryAndTempFile,
|
||||
Data = keyHash
|
||||
});
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// Make sure all operations on _fileWriterWorkerQueue are done.
|
||||
Synchronize();
|
||||
|
||||
_fileWriterWorkerQueue.Dispose();
|
||||
EnsureArchiveUpToDate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,273 +0,0 @@
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper to manipulate the disk shader cache.
|
||||
/// </summary>
|
||||
static class CacheHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Compute a cache manifest from runtime data.
|
||||
/// </summary>
|
||||
/// <param name="version">The version of the cache</param>
|
||||
/// <param name="graphicsApi">The graphics api used by the cache</param>
|
||||
/// <param name="hashType">The hash type of the cache</param>
|
||||
/// <param name="entries">The entries in the cache</param>
|
||||
/// <returns>The cache manifest from runtime data</returns>
|
||||
public static byte[] ComputeManifest(ulong version, CacheGraphicsApi graphicsApi, CacheHashType hashType, HashSet<Hash128> entries)
|
||||
{
|
||||
if (hashType != CacheHashType.XxHash128)
|
||||
{
|
||||
throw new NotImplementedException($"{hashType}");
|
||||
}
|
||||
|
||||
CacheManifestHeader manifestHeader = new CacheManifestHeader(version, graphicsApi, hashType);
|
||||
|
||||
byte[] data = new byte[Unsafe.SizeOf<CacheManifestHeader>() + entries.Count * Unsafe.SizeOf<Hash128>()];
|
||||
|
||||
// CacheManifestHeader has the same size as a Hash128.
|
||||
Span<Hash128> dataSpan = MemoryMarshal.Cast<byte, Hash128>(data.AsSpan()).Slice(1);
|
||||
|
||||
int i = 0;
|
||||
|
||||
foreach (Hash128 hash in entries)
|
||||
{
|
||||
dataSpan[i++] = hash;
|
||||
}
|
||||
|
||||
manifestHeader.UpdateChecksum(data.AsSpan(Unsafe.SizeOf<CacheManifestHeader>()));
|
||||
|
||||
MemoryMarshal.Write(data, ref manifestHeader);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the base directory of the shader cache for a given title id.
|
||||
/// </summary>
|
||||
/// <param name="titleId">The title id of the target application</param>
|
||||
/// <returns>The base directory of the shader cache for a given title id</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string GetBaseCacheDirectory(string titleId) => Path.Combine(AppDataManager.GamesDirPath, titleId, "cache", "shader");
|
||||
|
||||
/// <summary>
|
||||
/// Get the temp path to the cache data directory.
|
||||
/// </summary>
|
||||
/// <param name="cacheDirectory">The cache directory</param>
|
||||
/// <returns>The temp path to the cache data directory</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string GetCacheTempDataPath(string cacheDirectory) => Path.Combine(cacheDirectory, "temp");
|
||||
|
||||
/// <summary>
|
||||
/// The path to the cache archive file.
|
||||
/// </summary>
|
||||
/// <param name="cacheDirectory">The cache directory</param>
|
||||
/// <returns>The path to the cache archive file</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string GetArchivePath(string cacheDirectory) => Path.Combine(cacheDirectory, "cache.zip");
|
||||
|
||||
/// <summary>
|
||||
/// The path to the cache manifest file.
|
||||
/// </summary>
|
||||
/// <param name="cacheDirectory">The cache directory</param>
|
||||
/// <returns>The path to the cache manifest file</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string GetManifestPath(string cacheDirectory) => Path.Combine(cacheDirectory, "cache.info");
|
||||
|
||||
/// <summary>
|
||||
/// Create a new temp path to the given cached file via its hash.
|
||||
/// </summary>
|
||||
/// <param name="cacheDirectory">The cache directory</param>
|
||||
/// <param name="key">The hash of the cached data</param>
|
||||
/// <returns>New path to the given cached file</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string GenCacheTempFilePath(string cacheDirectory, Hash128 key) => Path.Combine(GetCacheTempDataPath(cacheDirectory), key.ToString());
|
||||
|
||||
/// <summary>
|
||||
/// Generate the path to the cache directory.
|
||||
/// </summary>
|
||||
/// <param name="baseCacheDirectory">The base of the cache directory</param>
|
||||
/// <param name="graphicsApi">The graphics api in use</param>
|
||||
/// <param name="shaderProvider">The name of the shader provider in use</param>
|
||||
/// <param name="cacheName">The name of the cache</param>
|
||||
/// <returns>The path to the cache directory</returns>
|
||||
public static string GenerateCachePath(string baseCacheDirectory, CacheGraphicsApi graphicsApi, string shaderProvider, string cacheName)
|
||||
{
|
||||
string graphicsApiName = graphicsApi switch
|
||||
{
|
||||
CacheGraphicsApi.OpenGL => "opengl",
|
||||
CacheGraphicsApi.OpenGLES => "opengles",
|
||||
CacheGraphicsApi.Vulkan => "vulkan",
|
||||
CacheGraphicsApi.DirectX => "directx",
|
||||
CacheGraphicsApi.Metal => "metal",
|
||||
CacheGraphicsApi.Guest => "guest",
|
||||
_ => throw new NotImplementedException(graphicsApi.ToString()),
|
||||
};
|
||||
|
||||
return Path.Combine(baseCacheDirectory, graphicsApiName, shaderProvider, cacheName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a cached file with the given hash that is present in the archive.
|
||||
/// </summary>
|
||||
/// <param name="archive">The archive in use</param>
|
||||
/// <param name="entry">The given hash</param>
|
||||
/// <returns>The cached file if present or null</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static byte[] ReadFromArchive(ZipFile archive, Hash128 entry)
|
||||
{
|
||||
if (archive != null)
|
||||
{
|
||||
ZipEntry archiveEntry = archive.GetEntry($"{entry}");
|
||||
|
||||
if (archiveEntry != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] result = new byte[archiveEntry.Size];
|
||||
|
||||
using (Stream archiveStream = archive.GetInputStream(archiveEntry))
|
||||
{
|
||||
archiveStream.Read(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Gpu, $"Cannot load cache file {entry} from archive");
|
||||
Logger.Error?.Print(LogClass.Gpu, e.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a cached file with the given hash that is not present in the archive.
|
||||
/// </summary>
|
||||
/// <param name="cacheDirectory">The cache directory</param>
|
||||
/// <param name="entry">The given hash</param>
|
||||
/// <returns>The cached file if present or null</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static byte[] ReadFromFile(string cacheDirectory, Hash128 entry)
|
||||
{
|
||||
string cacheTempFilePath = GenCacheTempFilePath(cacheDirectory, entry);
|
||||
|
||||
try
|
||||
{
|
||||
return File.ReadAllBytes(cacheTempFilePath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Gpu, $"Cannot load cache file at {cacheTempFilePath}");
|
||||
Logger.Error?.Print(LogClass.Gpu, e.ToString());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read transform feedback descriptors from guest.
|
||||
/// </summary>
|
||||
/// <param name="data">The raw guest transform feedback descriptors</param>
|
||||
/// <param name="header">The guest shader program header</param>
|
||||
/// <returns>The transform feedback descriptors read from guest</returns>
|
||||
public static TransformFeedbackDescriptorOld[] ReadTransformFeedbackInformation(ref ReadOnlySpan<byte> data, GuestShaderCacheHeader header)
|
||||
{
|
||||
if (header.TransformFeedbackCount != 0)
|
||||
{
|
||||
TransformFeedbackDescriptorOld[] result = new TransformFeedbackDescriptorOld[header.TransformFeedbackCount];
|
||||
|
||||
for (int i = 0; i < result.Length; i++)
|
||||
{
|
||||
GuestShaderCacheTransformFeedbackHeader feedbackHeader = MemoryMarshal.Read<GuestShaderCacheTransformFeedbackHeader>(data);
|
||||
|
||||
result[i] = new TransformFeedbackDescriptorOld(feedbackHeader.BufferIndex, feedbackHeader.Stride, data.Slice(Unsafe.SizeOf<GuestShaderCacheTransformFeedbackHeader>(), feedbackHeader.VaryingLocationsLength).ToArray());
|
||||
|
||||
data = data.Slice(Unsafe.SizeOf<GuestShaderCacheTransformFeedbackHeader>() + feedbackHeader.VaryingLocationsLength);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save temporary files not in archive.
|
||||
/// </summary>
|
||||
/// <param name="baseCacheDirectory">The base of the cache directory</param>
|
||||
/// <param name="archive">The archive to use</param>
|
||||
/// <param name="entries">The entries in the cache</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void EnsureArchiveUpToDate(string baseCacheDirectory, ZipFile archive, HashSet<Hash128> entries)
|
||||
{
|
||||
List<string> filesToDelete = new List<string>();
|
||||
|
||||
archive.BeginUpdate();
|
||||
|
||||
foreach (Hash128 hash in entries)
|
||||
{
|
||||
string cacheTempFilePath = GenCacheTempFilePath(baseCacheDirectory, hash);
|
||||
|
||||
if (File.Exists(cacheTempFilePath))
|
||||
{
|
||||
string cacheHash = $"{hash}";
|
||||
|
||||
ZipEntry entry = archive.GetEntry(cacheHash);
|
||||
|
||||
if (entry != null)
|
||||
{
|
||||
archive.Delete(entry);
|
||||
}
|
||||
|
||||
// We enforce deflate compression here to avoid possible incompatibilities on older version of Ryujinx that use System.IO.Compression.
|
||||
archive.Add(new StaticDiskDataSource(cacheTempFilePath), cacheHash, CompressionMethod.Deflated);
|
||||
filesToDelete.Add(cacheTempFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
archive.CommitUpdate();
|
||||
|
||||
foreach (string filePath in filesToDelete)
|
||||
{
|
||||
File.Delete(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsArchiveReadOnly(string archivePath)
|
||||
{
|
||||
FileInfo info = new FileInfo(archivePath);
|
||||
|
||||
if (!info.Exists)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (FileStream stream = info.Open(FileMode.Open, FileAccess.Read, FileShare.None))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,168 +0,0 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||
{
|
||||
/// <summary>
|
||||
/// Global Manager of the shader cache.
|
||||
/// </summary>
|
||||
class CacheManager : IDisposable
|
||||
{
|
||||
private CacheGraphicsApi _graphicsApi;
|
||||
private CacheHashType _hashType;
|
||||
private string _shaderProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Cache storing raw Maxwell shaders as programs.
|
||||
/// </summary>
|
||||
private CacheCollection _guestProgramCache;
|
||||
|
||||
/// <summary>
|
||||
/// Cache storing raw host programs.
|
||||
/// </summary>
|
||||
private CacheCollection _hostProgramCache;
|
||||
|
||||
/// <summary>
|
||||
/// Version of the guest cache shader (to increment when guest cache structure change).
|
||||
/// </summary>
|
||||
private const ulong GuestCacheVersion = 1759;
|
||||
|
||||
public bool IsReadOnly => _guestProgramCache.IsReadOnly || _hostProgramCache.IsReadOnly;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new cache manager instance
|
||||
/// </summary>
|
||||
/// <param name="graphicsApi">The graphics api in use</param>
|
||||
/// <param name="hashType">The hash type in use for the cache</param>
|
||||
/// <param name="shaderProvider">The name of the codegen provider</param>
|
||||
/// <param name="titleId">The guest application title ID</param>
|
||||
/// <param name="shaderCodeGenVersion">Version of the codegen</param>
|
||||
public CacheManager(CacheGraphicsApi graphicsApi, CacheHashType hashType, string shaderProvider, string titleId, ulong shaderCodeGenVersion)
|
||||
{
|
||||
_graphicsApi = graphicsApi;
|
||||
_hashType = hashType;
|
||||
_shaderProvider = shaderProvider;
|
||||
|
||||
string baseCacheDirectory = CacheHelper.GetBaseCacheDirectory(titleId);
|
||||
|
||||
_guestProgramCache = new CacheCollection(baseCacheDirectory, _hashType, CacheGraphicsApi.Guest, "", "program", GuestCacheVersion);
|
||||
_hostProgramCache = new CacheCollection(baseCacheDirectory, _hashType, _graphicsApi, _shaderProvider, "host", shaderCodeGenVersion);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Entries to remove from the manifest.
|
||||
/// </summary>
|
||||
/// <param name="entries">Entries to remove from the manifest of all caches</param>
|
||||
public void RemoveManifestEntries(HashSet<Hash128> entries)
|
||||
{
|
||||
_guestProgramCache.RemoveManifestEntriesAsync(entries);
|
||||
_hostProgramCache.RemoveManifestEntriesAsync(entries);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue a task to flush temporary files to the archives.
|
||||
/// </summary>
|
||||
public void FlushToArchive()
|
||||
{
|
||||
_guestProgramCache.FlushToArchiveAsync();
|
||||
_hostProgramCache.FlushToArchiveAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait for all tasks before this given point to be done.
|
||||
/// </summary>
|
||||
public void Synchronize()
|
||||
{
|
||||
_guestProgramCache.Synchronize();
|
||||
_hostProgramCache.Synchronize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save a shader program not present in the program cache.
|
||||
/// </summary>
|
||||
/// <param name="programCodeHash">Target program code hash</param>
|
||||
/// <param name="guestProgram">Guest program raw data</param>
|
||||
/// <param name="hostProgram">Host program raw data</param>
|
||||
public void SaveProgram(ref Hash128 programCodeHash, byte[] guestProgram, byte[] hostProgram)
|
||||
{
|
||||
_guestProgramCache.AddValue(ref programCodeHash, guestProgram);
|
||||
_hostProgramCache.AddValue(ref programCodeHash, hostProgram);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a host shader program not present in the program cache.
|
||||
/// </summary>
|
||||
/// <param name="programCodeHash">Target program code hash</param>
|
||||
/// <param name="data">Host program raw data</param>
|
||||
public void AddHostProgram(ref Hash128 programCodeHash, byte[] data)
|
||||
{
|
||||
_hostProgramCache.AddValue(ref programCodeHash, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace a host shader program present in the program cache.
|
||||
/// </summary>
|
||||
/// <param name="programCodeHash">Target program code hash</param>
|
||||
/// <param name="data">Host program raw data</param>
|
||||
public void ReplaceHostProgram(ref Hash128 programCodeHash, byte[] data)
|
||||
{
|
||||
_hostProgramCache.ReplaceValue(ref programCodeHash, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a shader program present in the program cache.
|
||||
/// </summary>
|
||||
/// <param name="programCodeHash">Target program code hash</param>
|
||||
public void RemoveProgram(ref Hash128 programCodeHash)
|
||||
{
|
||||
_guestProgramCache.RemoveValue(ref programCodeHash);
|
||||
_hostProgramCache.RemoveValue(ref programCodeHash);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all guest program hashes.
|
||||
/// </summary>
|
||||
/// <returns>All guest program hashes</returns>
|
||||
public ReadOnlySpan<Hash128> GetGuestProgramList()
|
||||
{
|
||||
return _guestProgramCache.HashTable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a host program by hash.
|
||||
/// </summary>
|
||||
/// <param name="hash">The given hash</param>
|
||||
/// <returns>The host program if present or null</returns>
|
||||
public byte[] GetHostProgramByHash(ref Hash128 hash)
|
||||
{
|
||||
return _hostProgramCache.GetValueRaw(ref hash);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a guest program by hash.
|
||||
/// </summary>
|
||||
/// <param name="hash">The given hash</param>
|
||||
/// <returns>The guest program if present or null</returns>
|
||||
public byte[] GetGuestProgramByHash(ref Hash128 hash)
|
||||
{
|
||||
return _guestProgramCache.GetValueRaw(ref hash);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_guestProgramCache.Dispose();
|
||||
_hostProgramCache.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
|
||||
{
|
||||
/// <summary>
|
||||
/// Graphics API type accepted by the shader cache.
|
||||
/// </summary>
|
||||
enum CacheGraphicsApi : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// OpenGL Core
|
||||
/// </summary>
|
||||
OpenGL,
|
||||
|
||||
/// <summary>
|
||||
/// OpenGL ES
|
||||
/// </summary>
|
||||
OpenGLES,
|
||||
|
||||
/// <summary>
|
||||
/// Vulkan
|
||||
/// </summary>
|
||||
Vulkan,
|
||||
|
||||
/// <summary>
|
||||
/// DirectX
|
||||
/// </summary>
|
||||
DirectX,
|
||||
|
||||
/// <summary>
|
||||
/// Metal
|
||||
/// </summary>
|
||||
Metal,
|
||||
|
||||
/// <summary>
|
||||
/// Guest, used to cache games raw shader programs.
|
||||
/// </summary>
|
||||
Guest
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
|
||||
{
|
||||
/// <summary>
|
||||
/// Hash algorithm accepted by the shader cache.
|
||||
/// </summary>
|
||||
enum CacheHashType : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// xxHash128
|
||||
/// </summary>
|
||||
XxHash128
|
||||
}
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
|
||||
{
|
||||
/// <summary>
|
||||
/// Header of the shader cache manifest.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)]
|
||||
struct CacheManifestHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// The version of the cache.
|
||||
/// </summary>
|
||||
public ulong Version;
|
||||
|
||||
/// <summary>
|
||||
/// The graphics api used for this cache.
|
||||
/// </summary>
|
||||
public CacheGraphicsApi GraphicsApi;
|
||||
|
||||
/// <summary>
|
||||
/// The hash type used for this cache.
|
||||
/// </summary>
|
||||
public CacheHashType HashType;
|
||||
|
||||
/// <summary>
|
||||
/// CRC-16 checksum over the data in the file.
|
||||
/// </summary>
|
||||
public ushort TableChecksum;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new cache manifest header.
|
||||
/// </summary>
|
||||
/// <param name="version">The version of the cache</param>
|
||||
/// <param name="graphicsApi">The graphics api used for this cache</param>
|
||||
/// <param name="hashType">The hash type used for this cache</param>
|
||||
public CacheManifestHeader(ulong version, CacheGraphicsApi graphicsApi, CacheHashType hashType)
|
||||
{
|
||||
Version = version;
|
||||
GraphicsApi = graphicsApi;
|
||||
HashType = hashType;
|
||||
TableChecksum = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the checksum in the header.
|
||||
/// </summary>
|
||||
/// <param name="data">The data to perform the checksum on</param>
|
||||
public void UpdateChecksum(ReadOnlySpan<byte> data)
|
||||
{
|
||||
TableChecksum = CalculateCrc16(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate a CRC-16 over data.
|
||||
/// </summary>
|
||||
/// <param name="data">The data to perform the CRC-16 on</param>
|
||||
/// <returns>A CRC-16 over data</returns>
|
||||
private static ushort CalculateCrc16(ReadOnlySpan<byte> data)
|
||||
{
|
||||
int crc = 0;
|
||||
|
||||
const ushort poly = 0x1021;
|
||||
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
crc ^= data[i] << 8;
|
||||
|
||||
for (int j = 0; j < 8; j++)
|
||||
{
|
||||
crc <<= 1;
|
||||
|
||||
if ((crc & 0x10000) != 0)
|
||||
{
|
||||
crc = (crc ^ poly) & 0xFFFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (ushort)crc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check the validity of the header.
|
||||
/// </summary>
|
||||
/// <param name="graphicsApi">The target graphics api in use</param>
|
||||
/// <param name="hashType">The target hash type in use</param>
|
||||
/// <param name="data">The data after this header</param>
|
||||
/// <returns>True if the header is valid</returns>
|
||||
/// <remarks>This doesn't check that versions match</remarks>
|
||||
public bool IsValid(CacheGraphicsApi graphicsApi, CacheHashType hashType, ReadOnlySpan<byte> data)
|
||||
{
|
||||
return GraphicsApi == graphicsApi && HashType == hashType && TableChecksum == CalculateCrc16(data);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
|
||||
{
|
||||
/// <summary>
|
||||
/// Header of a cached guest gpu accessor.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = 1)]
|
||||
struct GuestGpuAccessorHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// The count of texture descriptors.
|
||||
/// </summary>
|
||||
public int TextureDescriptorCount;
|
||||
|
||||
/// <summary>
|
||||
/// Local Size X for compute shaders.
|
||||
/// </summary>
|
||||
public int ComputeLocalSizeX;
|
||||
|
||||
/// <summary>
|
||||
/// Local Size Y for compute shaders.
|
||||
/// </summary>
|
||||
public int ComputeLocalSizeY;
|
||||
|
||||
/// <summary>
|
||||
/// Local Size Z for compute shaders.
|
||||
/// </summary>
|
||||
public int ComputeLocalSizeZ;
|
||||
|
||||
/// <summary>
|
||||
/// Local Memory size in bytes for compute shaders.
|
||||
/// </summary>
|
||||
public int ComputeLocalMemorySize;
|
||||
|
||||
/// <summary>
|
||||
/// Shared Memory size in bytes for compute shaders.
|
||||
/// </summary>
|
||||
public int ComputeSharedMemorySize;
|
||||
|
||||
/// <summary>
|
||||
/// Unused/reserved.
|
||||
/// </summary>
|
||||
public int Reserved1;
|
||||
|
||||
/// <summary>
|
||||
/// Current primitive topology for geometry shaders.
|
||||
/// </summary>
|
||||
public InputTopology PrimitiveTopology;
|
||||
|
||||
/// <summary>
|
||||
/// Tessellation parameters (packed to fit on a byte).
|
||||
/// </summary>
|
||||
public byte TessellationModePacked;
|
||||
|
||||
/// <summary>
|
||||
/// Unused/reserved.
|
||||
/// </summary>
|
||||
public byte Reserved2;
|
||||
|
||||
/// <summary>
|
||||
/// GPU boolean state that can influence shader compilation.
|
||||
/// </summary>
|
||||
public GuestGpuStateFlags StateFlags;
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
|
||||
{
|
||||
[Flags]
|
||||
enum GuestGpuStateFlags : byte
|
||||
{
|
||||
EarlyZForce = 1 << 0
|
||||
}
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
|
||||
{
|
||||
/// <summary>
|
||||
/// Represent a cached shader entry in a guest shader program.
|
||||
/// </summary>
|
||||
class GuestShaderCacheEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// The header of the cached shader entry.
|
||||
/// </summary>
|
||||
public GuestShaderCacheEntryHeader Header { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The code of this shader.
|
||||
/// </summary>
|
||||
/// <remarks>If a Vertex A is present, this also contains the code 2 section.</remarks>
|
||||
public byte[] Code { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The textures descriptors used for this shader.
|
||||
/// </summary>
|
||||
public Dictionary<int, GuestTextureDescriptor> TextureDescriptors { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new instance of <see cref="GuestShaderCacheEntry"/>.
|
||||
/// </summary>
|
||||
/// <param name="header">The header of the cached shader entry</param>
|
||||
/// <param name="code">The code of this shader</param>
|
||||
public GuestShaderCacheEntry(GuestShaderCacheEntryHeader header, byte[] code)
|
||||
{
|
||||
Header = header;
|
||||
Code = code;
|
||||
TextureDescriptors = new Dictionary<int, GuestTextureDescriptor>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a raw cached user shader program into an array of shader cache entry.
|
||||
/// </summary>
|
||||
/// <param name="data">The raw cached user shader program</param>
|
||||
/// <param name="fileHeader">The user shader program header</param>
|
||||
/// <returns>An array of shader cache entry</returns>
|
||||
public static GuestShaderCacheEntry[] Parse(ref ReadOnlySpan<byte> data, out GuestShaderCacheHeader fileHeader)
|
||||
{
|
||||
fileHeader = MemoryMarshal.Read<GuestShaderCacheHeader>(data);
|
||||
|
||||
data = data.Slice(Unsafe.SizeOf<GuestShaderCacheHeader>());
|
||||
|
||||
ReadOnlySpan<GuestShaderCacheEntryHeader> entryHeaders = MemoryMarshal.Cast<byte, GuestShaderCacheEntryHeader>(data.Slice(0, fileHeader.Count * Unsafe.SizeOf<GuestShaderCacheEntryHeader>()));
|
||||
|
||||
data = data.Slice(fileHeader.Count * Unsafe.SizeOf<GuestShaderCacheEntryHeader>());
|
||||
|
||||
GuestShaderCacheEntry[] result = new GuestShaderCacheEntry[fileHeader.Count];
|
||||
|
||||
for (int i = 0; i < result.Length; i++)
|
||||
{
|
||||
GuestShaderCacheEntryHeader header = entryHeaders[i];
|
||||
|
||||
// Ignore empty entries
|
||||
if (header.Size == 0 && header.SizeA == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
byte[] code = data.Slice(0, header.Size + header.SizeA).ToArray();
|
||||
|
||||
data = data.Slice(header.Size + header.SizeA);
|
||||
|
||||
result[i] = new GuestShaderCacheEntry(header, code);
|
||||
|
||||
ReadOnlySpan<GuestTextureDescriptor> textureDescriptors = MemoryMarshal.Cast<byte, GuestTextureDescriptor>(data.Slice(0, header.GpuAccessorHeader.TextureDescriptorCount * Unsafe.SizeOf<GuestTextureDescriptor>()));
|
||||
|
||||
foreach (GuestTextureDescriptor textureDescriptor in textureDescriptors)
|
||||
{
|
||||
result[i].TextureDescriptors.Add((int)textureDescriptor.Handle, textureDescriptor);
|
||||
}
|
||||
|
||||
data = data.Slice(header.GpuAccessorHeader.TextureDescriptorCount * Unsafe.SizeOf<GuestTextureDescriptor>());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
|
||||
{
|
||||
/// <summary>
|
||||
/// The header of a guest shader entry in a guest shader program.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 0x1, Size = 0x30)]
|
||||
struct GuestShaderCacheEntryHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// The stage of this shader.
|
||||
/// </summary>
|
||||
public ShaderStage Stage;
|
||||
|
||||
/// <summary>
|
||||
/// Unused/reserved.
|
||||
/// </summary>
|
||||
public byte Reserved1;
|
||||
|
||||
/// <summary>
|
||||
/// Unused/reserved.
|
||||
/// </summary>
|
||||
public byte Reserved2;
|
||||
|
||||
/// <summary>
|
||||
/// Unused/reserved.
|
||||
/// </summary>
|
||||
public byte Reserved3;
|
||||
|
||||
/// <summary>
|
||||
/// The size of the code section.
|
||||
/// </summary>
|
||||
public int Size;
|
||||
|
||||
/// <summary>
|
||||
/// The size of the code2 section if present. (Vertex A)
|
||||
/// </summary>
|
||||
public int SizeA;
|
||||
|
||||
/// <summary>
|
||||
/// Constant buffer 1 data size.
|
||||
/// </summary>
|
||||
public int Cb1DataSize;
|
||||
|
||||
/// <summary>
|
||||
/// The header of the cached gpu accessor.
|
||||
/// </summary>
|
||||
public GuestGpuAccessorHeader GpuAccessorHeader;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new guest shader entry header.
|
||||
/// </summary>
|
||||
/// <param name="stage">The stage of this shader</param>
|
||||
/// <param name="size">The size of the code section</param>
|
||||
/// <param name="sizeA">The size of the code2 section if present (Vertex A)</param>
|
||||
/// <param name="cb1DataSize">Constant buffer 1 data size</param>
|
||||
/// <param name="gpuAccessorHeader">The header of the cached gpu accessor</param>
|
||||
public GuestShaderCacheEntryHeader(ShaderStage stage, int size, int sizeA, int cb1DataSize, GuestGpuAccessorHeader gpuAccessorHeader) : this()
|
||||
{
|
||||
Stage = stage;
|
||||
Size = size;
|
||||
SizeA = sizeA;
|
||||
Cb1DataSize = cb1DataSize;
|
||||
GpuAccessorHeader = gpuAccessorHeader;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
|
||||
{
|
||||
/// <summary>
|
||||
/// The header of a shader program in the guest cache.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 0x1, Size = 0x10)]
|
||||
struct GuestShaderCacheHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// The count of shaders defining this program.
|
||||
/// </summary>
|
||||
public byte Count;
|
||||
|
||||
/// <summary>
|
||||
/// The count of transform feedback data used in this program.
|
||||
/// </summary>
|
||||
public byte TransformFeedbackCount;
|
||||
|
||||
/// <summary>
|
||||
/// Unused/reserved.
|
||||
/// </summary>
|
||||
public ushort Reserved1;
|
||||
|
||||
/// <summary>
|
||||
/// Unused/reserved.
|
||||
/// </summary>
|
||||
public ulong Reserved2;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new guest shader cache header.
|
||||
/// </summary>
|
||||
/// <param name="count">The count of shaders defining this program</param>
|
||||
/// <param name="transformFeedbackCount">The count of transform feedback data used in this program</param>
|
||||
public GuestShaderCacheHeader(byte count, byte transformFeedbackCount) : this()
|
||||
{
|
||||
Count = count;
|
||||
TransformFeedbackCount = transformFeedbackCount;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
|
||||
{
|
||||
/// <summary>
|
||||
/// Header for transform feedback.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)]
|
||||
struct GuestShaderCacheTransformFeedbackHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// The buffer index of the transform feedback.
|
||||
/// </summary>
|
||||
public int BufferIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The stride of the transform feedback.
|
||||
/// </summary>
|
||||
public int Stride;
|
||||
|
||||
/// <summary>
|
||||
/// The length of the varying location buffer of the transform feedback.
|
||||
/// </summary>
|
||||
public int VaryingLocationsLength;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved/unused.
|
||||
/// </summary>
|
||||
public int Reserved1;
|
||||
|
||||
public GuestShaderCacheTransformFeedbackHeader(int bufferIndex, int stride, int varyingLocationsLength) : this()
|
||||
{
|
||||
BufferIndex = bufferIndex;
|
||||
Stride = stride;
|
||||
VaryingLocationsLength = varyingLocationsLength;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains part of TextureDescriptor from <see cref="Image"/> used for shader codegen.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0xC, Pack = 1)]
|
||||
struct GuestTextureDescriptor : ITextureDescriptor
|
||||
{
|
||||
public uint Handle;
|
||||
public uint Format;
|
||||
public TextureTarget Target;
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool IsSrgb;
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool IsTextureCoordNormalized;
|
||||
public byte Reserved;
|
||||
|
||||
public uint UnpackFormat()
|
||||
{
|
||||
return Format;
|
||||
}
|
||||
|
||||
public bool UnpackSrgb()
|
||||
{
|
||||
return IsSrgb;
|
||||
}
|
||||
|
||||
public bool UnpackTextureCoordNormalized()
|
||||
{
|
||||
return IsTextureCoordNormalized;
|
||||
}
|
||||
|
||||
public TextureTarget UnpackTextureTarget()
|
||||
{
|
||||
return Target;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,222 +0,0 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
|
||||
{
|
||||
/// <summary>
|
||||
/// Host shader entry used for binding information.
|
||||
/// </summary>
|
||||
class HostShaderCacheEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// The header of the cached shader entry.
|
||||
/// </summary>
|
||||
public HostShaderCacheEntryHeader Header { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Cached constant buffers.
|
||||
/// </summary>
|
||||
public BufferDescriptor[] CBuffers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Cached storage buffers.
|
||||
/// </summary>
|
||||
public BufferDescriptor[] SBuffers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Cached texture descriptors.
|
||||
/// </summary>
|
||||
public TextureDescriptor[] Textures { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Cached image descriptors.
|
||||
/// </summary>
|
||||
public TextureDescriptor[] Images { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new instance of <see cref="HostShaderCacheEntry"/>.
|
||||
/// </summary>
|
||||
/// <param name="header">The header of the cached shader entry</param>
|
||||
/// <param name="cBuffers">Cached constant buffers</param>
|
||||
/// <param name="sBuffers">Cached storage buffers</param>
|
||||
/// <param name="textures">Cached texture descriptors</param>
|
||||
/// <param name="images">Cached image descriptors</param>
|
||||
private HostShaderCacheEntry(
|
||||
HostShaderCacheEntryHeader header,
|
||||
BufferDescriptor[] cBuffers,
|
||||
BufferDescriptor[] sBuffers,
|
||||
TextureDescriptor[] textures,
|
||||
TextureDescriptor[] images)
|
||||
{
|
||||
Header = header;
|
||||
CBuffers = cBuffers;
|
||||
SBuffers = sBuffers;
|
||||
Textures = textures;
|
||||
Images = images;
|
||||
}
|
||||
|
||||
private HostShaderCacheEntry()
|
||||
{
|
||||
Header = new HostShaderCacheEntryHeader();
|
||||
CBuffers = new BufferDescriptor[0];
|
||||
SBuffers = new BufferDescriptor[0];
|
||||
Textures = new TextureDescriptor[0];
|
||||
Images = new TextureDescriptor[0];
|
||||
}
|
||||
|
||||
private HostShaderCacheEntry(ShaderProgramInfo programInfo)
|
||||
{
|
||||
Header = new HostShaderCacheEntryHeader(programInfo.CBuffers.Count,
|
||||
programInfo.SBuffers.Count,
|
||||
programInfo.Textures.Count,
|
||||
programInfo.Images.Count,
|
||||
programInfo.UsesInstanceId,
|
||||
programInfo.UsesRtLayer,
|
||||
programInfo.ClipDistancesWritten,
|
||||
programInfo.FragmentOutputMap);
|
||||
CBuffers = programInfo.CBuffers.ToArray();
|
||||
SBuffers = programInfo.SBuffers.ToArray();
|
||||
Textures = programInfo.Textures.ToArray();
|
||||
Images = programInfo.Images.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert the host shader entry to a <see cref="ShaderProgramInfo"/>.
|
||||
/// </summary>
|
||||
/// <returns>A new <see cref="ShaderProgramInfo"/> from this instance</returns>
|
||||
internal ShaderProgramInfo ToShaderProgramInfo()
|
||||
{
|
||||
return new ShaderProgramInfo(
|
||||
CBuffers,
|
||||
SBuffers,
|
||||
Textures,
|
||||
Images,
|
||||
default,
|
||||
Header.UseFlags.HasFlag(UseFlags.InstanceId),
|
||||
Header.UseFlags.HasFlag(UseFlags.RtLayer),
|
||||
Header.ClipDistancesWritten,
|
||||
Header.FragmentOutputMap);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a raw cached user shader program into an array of shader cache entry.
|
||||
/// </summary>
|
||||
/// <param name="data">The raw cached host shader</param>
|
||||
/// <param name="programCode">The host shader program</param>
|
||||
/// <returns>An array of shader cache entry</returns>
|
||||
internal static HostShaderCacheEntry[] Parse(ReadOnlySpan<byte> data, out ReadOnlySpan<byte> programCode)
|
||||
{
|
||||
HostShaderCacheHeader fileHeader = MemoryMarshal.Read<HostShaderCacheHeader>(data);
|
||||
|
||||
data = data.Slice(Unsafe.SizeOf<HostShaderCacheHeader>());
|
||||
|
||||
ReadOnlySpan<HostShaderCacheEntryHeader> entryHeaders = MemoryMarshal.Cast<byte, HostShaderCacheEntryHeader>(data.Slice(0, fileHeader.Count * Unsafe.SizeOf<HostShaderCacheEntryHeader>()));
|
||||
|
||||
data = data.Slice(fileHeader.Count * Unsafe.SizeOf<HostShaderCacheEntryHeader>());
|
||||
|
||||
HostShaderCacheEntry[] result = new HostShaderCacheEntry[fileHeader.Count];
|
||||
|
||||
for (int i = 0; i < result.Length; i++)
|
||||
{
|
||||
HostShaderCacheEntryHeader header = entryHeaders[i];
|
||||
|
||||
if (!header.InUse)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int cBufferDescriptorsSize = header.CBuffersCount * Unsafe.SizeOf<BufferDescriptor>();
|
||||
int sBufferDescriptorsSize = header.SBuffersCount * Unsafe.SizeOf<BufferDescriptor>();
|
||||
int textureDescriptorsSize = header.TexturesCount * Unsafe.SizeOf<TextureDescriptor>();
|
||||
int imageDescriptorsSize = header.ImagesCount * Unsafe.SizeOf<TextureDescriptor>();
|
||||
|
||||
ReadOnlySpan<BufferDescriptor> cBuffers = MemoryMarshal.Cast<byte, BufferDescriptor>(data.Slice(0, cBufferDescriptorsSize));
|
||||
data = data.Slice(cBufferDescriptorsSize);
|
||||
|
||||
ReadOnlySpan<BufferDescriptor> sBuffers = MemoryMarshal.Cast<byte, BufferDescriptor>(data.Slice(0, sBufferDescriptorsSize));
|
||||
data = data.Slice(sBufferDescriptorsSize);
|
||||
|
||||
ReadOnlySpan<TextureDescriptor> textureDescriptors = MemoryMarshal.Cast<byte, TextureDescriptor>(data.Slice(0, textureDescriptorsSize));
|
||||
data = data.Slice(textureDescriptorsSize);
|
||||
|
||||
ReadOnlySpan<TextureDescriptor> imageDescriptors = MemoryMarshal.Cast<byte, TextureDescriptor>(data.Slice(0, imageDescriptorsSize));
|
||||
data = data.Slice(imageDescriptorsSize);
|
||||
|
||||
result[i] = new HostShaderCacheEntry(header, cBuffers.ToArray(), sBuffers.ToArray(), textureDescriptors.ToArray(), imageDescriptors.ToArray());
|
||||
}
|
||||
|
||||
programCode = data.Slice(0, fileHeader.CodeSize);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new host shader cache file.
|
||||
/// </summary>
|
||||
/// <param name="programCode">The host shader program</param>
|
||||
/// <param name="codeHolders">The shaders code holder</param>
|
||||
/// <returns>Raw data of a new host shader cache file</returns>
|
||||
internal static byte[] Create(ReadOnlySpan<byte> programCode, CachedShaderStage[] codeHolders)
|
||||
{
|
||||
HostShaderCacheHeader header = new HostShaderCacheHeader((byte)codeHolders.Length, programCode.Length);
|
||||
|
||||
HostShaderCacheEntry[] entries = new HostShaderCacheEntry[codeHolders.Length];
|
||||
|
||||
for (int i = 0; i < codeHolders.Length; i++)
|
||||
{
|
||||
if (codeHolders[i] == null)
|
||||
{
|
||||
entries[i] = new HostShaderCacheEntry();
|
||||
}
|
||||
else
|
||||
{
|
||||
entries[i] = new HostShaderCacheEntry(codeHolders[i].Info);
|
||||
}
|
||||
}
|
||||
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
{
|
||||
BinaryWriter writer = new BinaryWriter(stream);
|
||||
|
||||
writer.WriteStruct(header);
|
||||
|
||||
foreach (HostShaderCacheEntry entry in entries)
|
||||
{
|
||||
writer.WriteStruct(entry.Header);
|
||||
}
|
||||
|
||||
foreach (HostShaderCacheEntry entry in entries)
|
||||
{
|
||||
foreach (BufferDescriptor cBuffer in entry.CBuffers)
|
||||
{
|
||||
writer.WriteStruct(cBuffer);
|
||||
}
|
||||
|
||||
foreach (BufferDescriptor sBuffer in entry.SBuffers)
|
||||
{
|
||||
writer.WriteStruct(sBuffer);
|
||||
}
|
||||
|
||||
foreach (TextureDescriptor texture in entry.Textures)
|
||||
{
|
||||
writer.WriteStruct(texture);
|
||||
}
|
||||
|
||||
foreach (TextureDescriptor image in entry.Images)
|
||||
{
|
||||
writer.WriteStruct(image);
|
||||
}
|
||||
}
|
||||
|
||||
writer.Write(programCode);
|
||||
|
||||
return stream.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,114 +0,0 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
|
||||
{
|
||||
/// <summary>
|
||||
/// Flags indicating if the shader accesses certain built-ins, such as the instance ID.
|
||||
/// </summary>
|
||||
enum UseFlags : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// None of the built-ins are used.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whenever the vertex shader reads the gl_InstanceID built-in.
|
||||
/// </summary>
|
||||
InstanceId = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whenever any of the VTG stages writes to the gl_Layer built-in.
|
||||
/// </summary>
|
||||
RtLayer = 1 << 1
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Host shader entry header used for binding information.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x18)]
|
||||
struct HostShaderCacheEntryHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// Count of constant buffer descriptors.
|
||||
/// </summary>
|
||||
public int CBuffersCount;
|
||||
|
||||
/// <summary>
|
||||
/// Count of storage buffer descriptors.
|
||||
/// </summary>
|
||||
public int SBuffersCount;
|
||||
|
||||
/// <summary>
|
||||
/// Count of texture descriptors.
|
||||
/// </summary>
|
||||
public int TexturesCount;
|
||||
|
||||
/// <summary>
|
||||
/// Count of image descriptors.
|
||||
/// </summary>
|
||||
public int ImagesCount;
|
||||
|
||||
/// <summary>
|
||||
/// Flags indicating if the shader accesses certain built-ins, such as the instance ID.
|
||||
/// </summary>
|
||||
public UseFlags UseFlags;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if this entry is in use.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool InUse;
|
||||
|
||||
/// <summary>
|
||||
/// Mask of clip distances that are written to on the shader.
|
||||
/// </summary>
|
||||
public byte ClipDistancesWritten;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved / unused.
|
||||
/// </summary>
|
||||
public byte Reserved;
|
||||
|
||||
/// <summary>
|
||||
/// Mask of components written by the fragment shader stage.
|
||||
/// </summary>
|
||||
public int FragmentOutputMap;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new host shader cache entry header.
|
||||
/// </summary>
|
||||
/// <param name="cBuffersCount">Count of constant buffer descriptors</param>
|
||||
/// <param name="sBuffersCount">Count of storage buffer descriptors</param>
|
||||
/// <param name="texturesCount">Count of texture descriptors</param>
|
||||
/// <param name="imagesCount">Count of image descriptors</param>
|
||||
/// <param name="usesInstanceId">Set to true if the shader uses instance id</param>
|
||||
/// <param name="clipDistancesWritten">Mask of clip distances that are written to on the shader</param>
|
||||
/// <param name="fragmentOutputMap">Mask of components written by the fragment shader stage</param>
|
||||
public HostShaderCacheEntryHeader(
|
||||
int cBuffersCount,
|
||||
int sBuffersCount,
|
||||
int texturesCount,
|
||||
int imagesCount,
|
||||
bool usesInstanceId,
|
||||
bool usesRtLayer,
|
||||
byte clipDistancesWritten,
|
||||
int fragmentOutputMap) : this()
|
||||
{
|
||||
CBuffersCount = cBuffersCount;
|
||||
SBuffersCount = sBuffersCount;
|
||||
TexturesCount = texturesCount;
|
||||
ImagesCount = imagesCount;
|
||||
ClipDistancesWritten = clipDistancesWritten;
|
||||
FragmentOutputMap = fragmentOutputMap;
|
||||
InUse = true;
|
||||
|
||||
UseFlags = usesInstanceId ? UseFlags.InstanceId : UseFlags.None;
|
||||
|
||||
if (usesRtLayer)
|
||||
{
|
||||
UseFlags |= UseFlags.RtLayer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
|
||||
{
|
||||
/// <summary>
|
||||
/// The header of a shader program in the guest cache.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 0x1, Size = 0x10)]
|
||||
struct HostShaderCacheHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// The count of shaders defining this program.
|
||||
/// </summary>
|
||||
public byte Count;
|
||||
|
||||
/// <summary>
|
||||
/// Unused/reserved.
|
||||
/// </summary>
|
||||
public byte Reserved1;
|
||||
|
||||
/// <summary>
|
||||
/// Unused/reserved.
|
||||
/// </summary>
|
||||
public ushort Reserved2;
|
||||
|
||||
/// <summary>
|
||||
/// Size of the shader binary.
|
||||
/// </summary>
|
||||
public int CodeSize;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new host shader cache header.
|
||||
/// </summary>
|
||||
/// <param name="count">The count of shaders defining this program</param>
|
||||
/// <param name="codeSize">The size of the shader binary</param>
|
||||
public HostShaderCacheHeader(byte count, int codeSize) : this()
|
||||
{
|
||||
Count = count;
|
||||
CodeSize = codeSize;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,258 +0,0 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Threed;
|
||||
using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
|
||||
using Ryujinx.Graphics.Gpu.Shader.DiskCache;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||
{
|
||||
/// <summary>
|
||||
/// Class handling shader cache migrations.
|
||||
/// </summary>
|
||||
static class Migration
|
||||
{
|
||||
// Last codegen version before the migration to the new cache.
|
||||
private const ulong ShaderCodeGenVersion = 3054;
|
||||
|
||||
/// <summary>
|
||||
/// Migrates from the old cache format to the new one.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="hostStorage">Disk cache host storage (used to create the new shader files)</param>
|
||||
/// <returns>Number of migrated shaders</returns>
|
||||
public static int MigrateFromLegacyCache(GpuContext context, DiskCacheHostStorage hostStorage)
|
||||
{
|
||||
string baseCacheDirectory = CacheHelper.GetBaseCacheDirectory(GraphicsConfig.TitleId);
|
||||
string cacheDirectory = CacheHelper.GenerateCachePath(baseCacheDirectory, CacheGraphicsApi.Guest, "", "program");
|
||||
|
||||
// If the directory does not exist, we have no old cache.
|
||||
// Exist early as the CacheManager constructor will create the directories.
|
||||
if (!Directory.Exists(cacheDirectory))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (GraphicsConfig.EnableShaderCache && GraphicsConfig.TitleId != null)
|
||||
{
|
||||
CacheManager cacheManager = new CacheManager(CacheGraphicsApi.OpenGL, CacheHashType.XxHash128, "glsl", GraphicsConfig.TitleId, ShaderCodeGenVersion);
|
||||
|
||||
bool isReadOnly = cacheManager.IsReadOnly;
|
||||
|
||||
HashSet<Hash128> invalidEntries = null;
|
||||
|
||||
if (isReadOnly)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, "Loading shader cache in read-only mode (cache in use by another program!)");
|
||||
}
|
||||
else
|
||||
{
|
||||
invalidEntries = new HashSet<Hash128>();
|
||||
}
|
||||
|
||||
ReadOnlySpan<Hash128> guestProgramList = cacheManager.GetGuestProgramList();
|
||||
|
||||
for (int programIndex = 0; programIndex < guestProgramList.Length; programIndex++)
|
||||
{
|
||||
Hash128 key = guestProgramList[programIndex];
|
||||
|
||||
byte[] guestProgram = cacheManager.GetGuestProgramByHash(ref key);
|
||||
|
||||
if (guestProgram == null)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Gpu, $"Ignoring orphan shader hash {key} in cache (is the cache incomplete?)");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
ReadOnlySpan<byte> guestProgramReadOnlySpan = guestProgram;
|
||||
|
||||
ReadOnlySpan<GuestShaderCacheEntry> cachedShaderEntries = GuestShaderCacheEntry.Parse(ref guestProgramReadOnlySpan, out GuestShaderCacheHeader fileHeader);
|
||||
|
||||
if (cachedShaderEntries[0].Header.Stage == ShaderStage.Compute)
|
||||
{
|
||||
Debug.Assert(cachedShaderEntries.Length == 1);
|
||||
|
||||
GuestShaderCacheEntry entry = cachedShaderEntries[0];
|
||||
|
||||
byte[] code = entry.Code.AsSpan(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray();
|
||||
|
||||
Span<byte> codeSpan = entry.Code;
|
||||
byte[] cb1Data = codeSpan.Slice(codeSpan.Length - entry.Header.Cb1DataSize).ToArray();
|
||||
|
||||
ShaderProgramInfo info = new ShaderProgramInfo(
|
||||
Array.Empty<BufferDescriptor>(),
|
||||
Array.Empty<BufferDescriptor>(),
|
||||
Array.Empty<TextureDescriptor>(),
|
||||
Array.Empty<TextureDescriptor>(),
|
||||
ShaderStage.Compute,
|
||||
false,
|
||||
false,
|
||||
0,
|
||||
0);
|
||||
|
||||
GpuChannelComputeState computeState = new GpuChannelComputeState(
|
||||
entry.Header.GpuAccessorHeader.ComputeLocalSizeX,
|
||||
entry.Header.GpuAccessorHeader.ComputeLocalSizeY,
|
||||
entry.Header.GpuAccessorHeader.ComputeLocalSizeZ,
|
||||
entry.Header.GpuAccessorHeader.ComputeLocalMemorySize,
|
||||
entry.Header.GpuAccessorHeader.ComputeSharedMemorySize);
|
||||
|
||||
ShaderSpecializationState specState = new ShaderSpecializationState(computeState);
|
||||
|
||||
foreach (var td in entry.TextureDescriptors)
|
||||
{
|
||||
var handle = td.Key;
|
||||
var data = td.Value;
|
||||
|
||||
specState.RegisterTexture(
|
||||
0,
|
||||
handle,
|
||||
-1,
|
||||
data.UnpackFormat(),
|
||||
data.UnpackSrgb(),
|
||||
data.UnpackTextureTarget(),
|
||||
data.UnpackTextureCoordNormalized());
|
||||
}
|
||||
|
||||
CachedShaderStage shader = new CachedShaderStage(info, code, cb1Data);
|
||||
CachedShaderProgram program = new CachedShaderProgram(null, specState, shader);
|
||||
|
||||
hostStorage.AddShader(context, program, ReadOnlySpan<byte>.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(cachedShaderEntries.Length == Constants.ShaderStages);
|
||||
|
||||
CachedShaderStage[] shaders = new CachedShaderStage[Constants.ShaderStages + 1];
|
||||
List<ShaderProgram> shaderPrograms = new List<ShaderProgram>();
|
||||
|
||||
TransformFeedbackDescriptorOld[] tfd = CacheHelper.ReadTransformFeedbackInformation(ref guestProgramReadOnlySpan, fileHeader);
|
||||
|
||||
GuestShaderCacheEntry[] entries = cachedShaderEntries.ToArray();
|
||||
|
||||
GuestGpuAccessorHeader accessorHeader = entries[0].Header.GpuAccessorHeader;
|
||||
|
||||
TessMode tessMode = new TessMode();
|
||||
|
||||
int tessPatchType = accessorHeader.TessellationModePacked & 3;
|
||||
int tessSpacing = (accessorHeader.TessellationModePacked >> 2) & 3;
|
||||
bool tessCw = (accessorHeader.TessellationModePacked & 0x10) != 0;
|
||||
|
||||
tessMode.Packed = (uint)tessPatchType;
|
||||
tessMode.Packed |= (uint)(tessSpacing << 4);
|
||||
|
||||
if (tessCw)
|
||||
{
|
||||
tessMode.Packed |= 0x100;
|
||||
}
|
||||
|
||||
PrimitiveTopology topology = accessorHeader.PrimitiveTopology switch
|
||||
{
|
||||
InputTopology.Lines => PrimitiveTopology.Lines,
|
||||
InputTopology.LinesAdjacency => PrimitiveTopology.LinesAdjacency,
|
||||
InputTopology.Triangles => PrimitiveTopology.Triangles,
|
||||
InputTopology.TrianglesAdjacency => PrimitiveTopology.TrianglesAdjacency,
|
||||
_ => PrimitiveTopology.Points
|
||||
};
|
||||
|
||||
GpuChannelGraphicsState graphicsState = new GpuChannelGraphicsState(
|
||||
accessorHeader.StateFlags.HasFlag(GuestGpuStateFlags.EarlyZForce),
|
||||
topology,
|
||||
tessMode,
|
||||
false,
|
||||
false,
|
||||
false);
|
||||
|
||||
TransformFeedbackDescriptor[] tfdNew = null;
|
||||
|
||||
if (tfd != null)
|
||||
{
|
||||
tfdNew = new TransformFeedbackDescriptor[tfd.Length];
|
||||
|
||||
for (int tfIndex = 0; tfIndex < tfd.Length; tfIndex++)
|
||||
{
|
||||
Array32<uint> varyingLocations = new Array32<uint>();
|
||||
Span<byte> varyingLocationsSpan = MemoryMarshal.Cast<uint, byte>(varyingLocations.ToSpan());
|
||||
tfd[tfIndex].VaryingLocations.CopyTo(varyingLocationsSpan.Slice(0, tfd[tfIndex].VaryingLocations.Length));
|
||||
|
||||
tfdNew[tfIndex] = new TransformFeedbackDescriptor(
|
||||
tfd[tfIndex].BufferIndex,
|
||||
tfd[tfIndex].Stride,
|
||||
tfd[tfIndex].VaryingLocations.Length,
|
||||
ref varyingLocations);
|
||||
}
|
||||
}
|
||||
|
||||
ShaderSpecializationState specState = new ShaderSpecializationState(graphicsState, tfdNew);
|
||||
|
||||
for (int i = 0; i < entries.Length; i++)
|
||||
{
|
||||
GuestShaderCacheEntry entry = entries[i];
|
||||
|
||||
if (entry == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ShaderProgramInfo info = new ShaderProgramInfo(
|
||||
Array.Empty<BufferDescriptor>(),
|
||||
Array.Empty<BufferDescriptor>(),
|
||||
Array.Empty<TextureDescriptor>(),
|
||||
Array.Empty<TextureDescriptor>(),
|
||||
(ShaderStage)(i + 1),
|
||||
false,
|
||||
false,
|
||||
0,
|
||||
0);
|
||||
|
||||
// NOTE: Vertex B comes first in the shader cache.
|
||||
byte[] code = entry.Code.AsSpan(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray();
|
||||
byte[] code2 = entry.Header.SizeA != 0 ? entry.Code.AsSpan(entry.Header.Size, entry.Header.SizeA).ToArray() : null;
|
||||
|
||||
Span<byte> codeSpan = entry.Code;
|
||||
byte[] cb1Data = codeSpan.Slice(codeSpan.Length - entry.Header.Cb1DataSize).ToArray();
|
||||
|
||||
shaders[i + 1] = new CachedShaderStage(info, code, cb1Data);
|
||||
|
||||
if (code2 != null)
|
||||
{
|
||||
shaders[0] = new CachedShaderStage(null, code2, cb1Data);
|
||||
}
|
||||
|
||||
foreach (var td in entry.TextureDescriptors)
|
||||
{
|
||||
var handle = td.Key;
|
||||
var data = td.Value;
|
||||
|
||||
specState.RegisterTexture(
|
||||
i,
|
||||
handle,
|
||||
-1,
|
||||
data.UnpackFormat(),
|
||||
data.UnpackSrgb(),
|
||||
data.UnpackTextureTarget(),
|
||||
data.UnpackTextureCoordNormalized());
|
||||
}
|
||||
}
|
||||
|
||||
CachedShaderProgram program = new CachedShaderProgram(null, specState, shaders);
|
||||
|
||||
hostStorage.AddShader(context, program, ReadOnlySpan<byte>.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
return guestProgramList.Length;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||
{
|
||||
struct TransformFeedbackDescriptorOld
|
||||
{
|
||||
public int BufferIndex { get; }
|
||||
public int Stride { get; }
|
||||
|
||||
public byte[] VaryingLocations { get; }
|
||||
|
||||
public TransformFeedbackDescriptorOld(int bufferIndex, int stride, byte[] varyingLocations)
|
||||
{
|
||||
BufferIndex = bufferIndex;
|
||||
Stride = stride;
|
||||
VaryingLocations = varyingLocations ?? throw new ArgumentNullException(nameof(varyingLocations));
|
||||
}
|
||||
}
|
||||
}
|
@ -83,7 +83,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
{
|
||||
_context = context;
|
||||
_hostStorage = hostStorage;
|
||||
_fileWriterWorkerQueue = new AsyncWorkQueue<CacheFileOperationTask>(ProcessTask, "Gpu.BackgroundDiskCacheWriter");
|
||||
_fileWriterWorkerQueue = new AsyncWorkQueue<CacheFileOperationTask>(ProcessTask, "GPU.BackgroundDiskCacheWriter");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,6 +1,8 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
@ -16,7 +18,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
private readonly ShaderSpecializationState _oldSpecState;
|
||||
private readonly ShaderSpecializationState _newSpecState;
|
||||
private readonly int _stageIndex;
|
||||
private ResourceCounts _resourceCounts;
|
||||
private readonly bool _isVulkan;
|
||||
private readonly ResourceCounts _resourceCounts;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the cached GPU state accessor for shader translation.
|
||||
@ -34,13 +37,14 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
ShaderSpecializationState oldSpecState,
|
||||
ShaderSpecializationState newSpecState,
|
||||
ResourceCounts counts,
|
||||
int stageIndex) : base(context)
|
||||
int stageIndex) : base(context, counts, stageIndex)
|
||||
{
|
||||
_data = data;
|
||||
_cb1Data = cb1Data;
|
||||
_oldSpecState = oldSpecState;
|
||||
_newSpecState = newSpecState;
|
||||
_stageIndex = stageIndex;
|
||||
_isVulkan = context.Capabilities.Api == TargetApi.Vulkan;
|
||||
_resourceCounts = counts;
|
||||
}
|
||||
|
||||
@ -74,27 +78,33 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int QueryBindingConstantBuffer(int index)
|
||||
public AlphaTestOp QueryAlphaTestCompare()
|
||||
{
|
||||
return _resourceCounts.UniformBuffersCount++;
|
||||
if (!_isVulkan || !_oldSpecState.GraphicsState.AlphaTestEnable)
|
||||
{
|
||||
return AlphaTestOp.Always;
|
||||
}
|
||||
|
||||
return _oldSpecState.GraphicsState.AlphaTestCompare switch
|
||||
{
|
||||
CompareOp.Never or CompareOp.NeverGl => AlphaTestOp.Never,
|
||||
CompareOp.Less or CompareOp.LessGl => AlphaTestOp.Less,
|
||||
CompareOp.Equal or CompareOp.EqualGl => AlphaTestOp.Equal,
|
||||
CompareOp.LessOrEqual or CompareOp.LessOrEqualGl => AlphaTestOp.LessOrEqual,
|
||||
CompareOp.Greater or CompareOp.GreaterGl => AlphaTestOp.Greater,
|
||||
CompareOp.NotEqual or CompareOp.NotEqualGl => AlphaTestOp.NotEqual,
|
||||
CompareOp.GreaterOrEqual or CompareOp.GreaterOrEqualGl => AlphaTestOp.GreaterOrEqual,
|
||||
_ => AlphaTestOp.Always
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int QueryBindingStorageBuffer(int index)
|
||||
{
|
||||
return _resourceCounts.StorageBuffersCount++;
|
||||
}
|
||||
public float QueryAlphaTestReference() => _oldSpecState.GraphicsState.AlphaTestReference;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int QueryBindingTexture(int index)
|
||||
public AttributeType QueryAttributeType(int location)
|
||||
{
|
||||
return _resourceCounts.TexturesCount++;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int QueryBindingImage(int index)
|
||||
{
|
||||
return _resourceCounts.ImagesCount++;
|
||||
return _oldSpecState.GraphicsState.AttributeTypes[location];
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@ -126,6 +136,18 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
return ConvertToInputTopology(_oldSpecState.GraphicsState.Topology, _oldSpecState.GraphicsState.TessellationMode);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool QueryProgramPointSize()
|
||||
{
|
||||
return _oldSpecState.GraphicsState.ProgramPointSizeEnable;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float QueryPointSize()
|
||||
{
|
||||
return _oldSpecState.GraphicsState.PointSize;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool QueryTessCw()
|
||||
{
|
||||
@ -166,6 +188,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
return _oldSpecState.GetCoordNormalized(_stageIndex, handle, cbufSlot);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool QueryTransformDepthMinusOneToOne()
|
||||
{
|
||||
return _oldSpecState.GraphicsState.DepthMode;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool QueryTransformFeedbackEnabled()
|
||||
{
|
||||
|
@ -14,7 +14,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
private const uint TocMagic = (byte)'T' | ((byte)'O' << 8) | ((byte)'C' << 16) | ((byte)'G' << 24);
|
||||
|
||||
private const ushort VersionMajor = 1;
|
||||
private const ushort VersionMinor = 0;
|
||||
private const ushort VersionMinor = 1;
|
||||
private const uint VersionPacked = ((uint)VersionMajor << 16) | VersionMinor;
|
||||
|
||||
private const string TocFileName = "guest.toc";
|
||||
@ -193,8 +193,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
/// <param name="tocFileStream">Guest TOC file stream</param>
|
||||
/// <param name="dataFileStream">Guest data file stream</param>
|
||||
/// <param name="index">Guest shader index</param>
|
||||
/// <returns>Tuple with the guest code and constant buffer 1 data, respectively</returns>
|
||||
public (byte[], byte[]) LoadShader(Stream tocFileStream, Stream dataFileStream, int index)
|
||||
/// <returns>Guest code and constant buffer 1 data</returns>
|
||||
public GuestCodeAndCbData LoadShader(Stream tocFileStream, Stream dataFileStream, int index)
|
||||
{
|
||||
if (_cache == null || index >= _cache.Length)
|
||||
{
|
||||
@ -226,7 +226,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
_cache[index] = (guestCode, cb1Data);
|
||||
}
|
||||
|
||||
return (guestCode, cb1Data);
|
||||
return new GuestCodeAndCbData(guestCode, cb1Data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,5 +1,6 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
@ -19,9 +20,9 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
private const uint TexdMagic = (byte)'T' | ((byte)'E' << 8) | ((byte)'X' << 16) | ((byte)'D' << 24);
|
||||
|
||||
private const ushort FileFormatVersionMajor = 1;
|
||||
private const ushort FileFormatVersionMinor = 1;
|
||||
private const ushort FileFormatVersionMinor = 2;
|
||||
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
||||
private const uint CodeGenVersion = 3469;
|
||||
private const uint CodeGenVersion = 13;
|
||||
|
||||
private const string SharedTocFileName = "shared.toc";
|
||||
private const string SharedDataFileName = "shared.data";
|
||||
@ -56,14 +57,14 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
public uint Padding;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved space, to be used in the future. Write as zero.
|
||||
/// Timestamp of when the file was first created.
|
||||
/// </summary>
|
||||
public ulong Reserved;
|
||||
public ulong Timestamp;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved space, to be used in the future. Write as zero.
|
||||
/// </summary>
|
||||
public ulong Reserved2;
|
||||
public ulong Reserved;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -77,9 +78,14 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
public ulong Offset;
|
||||
|
||||
/// <summary>
|
||||
/// Size.
|
||||
/// Size of uncompressed data.
|
||||
/// </summary>
|
||||
public uint Size;
|
||||
public uint UncompressedSize;
|
||||
|
||||
/// <summary>
|
||||
/// Size of compressed data.
|
||||
/// </summary>
|
||||
public uint CompressedSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -185,7 +191,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (int)((new FileInfo(tocFilePath).Length - Unsafe.SizeOf<TocHeader>()) / sizeof(ulong));
|
||||
return Math.Max((int)((new FileInfo(tocFilePath).Length - Unsafe.SizeOf<TocHeader>()) / sizeof(ulong)), 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -324,7 +330,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
stagesBitMask = 1;
|
||||
}
|
||||
|
||||
CachedShaderStage[] shaders = new CachedShaderStage[isCompute ? 1 : Constants.ShaderStages + 1];
|
||||
GuestCodeAndCbData?[] guestShaders = new GuestCodeAndCbData?[isCompute ? 1 : Constants.ShaderStages + 1];
|
||||
|
||||
DataEntryPerStage stageEntry = new DataEntryPerStage();
|
||||
|
||||
@ -334,15 +340,11 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
|
||||
dataReader.Read(ref stageEntry);
|
||||
|
||||
ShaderProgramInfo info = stageIndex != 0 || isCompute ? ReadShaderProgramInfo(ref dataReader) : null;
|
||||
|
||||
(byte[] guestCode, byte[] cb1Data) = _guestStorage.LoadShader(
|
||||
guestShaders[stageIndex] = _guestStorage.LoadShader(
|
||||
guestTocFileStream,
|
||||
guestDataFileStream,
|
||||
stageEntry.GuestCodeIndex);
|
||||
|
||||
shaders[stageIndex] = new CachedShaderStage(info, guestCode, cb1Data);
|
||||
|
||||
stagesBitMask &= ~(1u << stageIndex);
|
||||
}
|
||||
|
||||
@ -351,17 +353,39 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
|
||||
if (loadHostCache)
|
||||
{
|
||||
byte[] hostCode = ReadHostCode(context, ref hostTocFileStream, ref hostDataFileStream, programIndex);
|
||||
(byte[] hostCode, CachedShaderStage[] shaders) = ReadHostCode(
|
||||
context,
|
||||
ref hostTocFileStream,
|
||||
ref hostDataFileStream,
|
||||
guestShaders,
|
||||
programIndex,
|
||||
header.Timestamp);
|
||||
|
||||
if (hostCode != null)
|
||||
{
|
||||
bool hasFragmentShader = shaders.Length > 5 && shaders[5] != null;
|
||||
int fragmentOutputMap = hasFragmentShader ? shaders[5].Info.FragmentOutputMap : -1;
|
||||
IProgram hostProgram = context.Renderer.LoadProgramBinary(hostCode, hasFragmentShader, new ShaderInfo(fragmentOutputMap));
|
||||
|
||||
ShaderInfo shaderInfo = specState.PipelineState.HasValue
|
||||
? new ShaderInfo(fragmentOutputMap, specState.PipelineState.Value, fromCache: true)
|
||||
: new ShaderInfo(fragmentOutputMap, fromCache: true);
|
||||
|
||||
IProgram hostProgram;
|
||||
|
||||
if (context.Capabilities.Api == TargetApi.Vulkan)
|
||||
{
|
||||
ShaderSource[] shaderSources = ShaderBinarySerializer.Unpack(shaders, hostCode, isCompute);
|
||||
|
||||
hostProgram = context.Renderer.CreateProgram(shaderSources, shaderInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
hostProgram = context.Renderer.LoadProgramBinary(hostCode, hasFragmentShader, shaderInfo);
|
||||
}
|
||||
|
||||
CachedShaderProgram program = new CachedShaderProgram(hostProgram, specState, shaders);
|
||||
|
||||
loader.QueueHostProgram(program, hostProgram, programIndex, isCompute);
|
||||
loader.QueueHostProgram(program, hostCode, programIndex, isCompute);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -371,7 +395,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
|
||||
if (!loadHostCache)
|
||||
{
|
||||
loader.QueueGuestProgram(shaders, specState, programIndex, isCompute);
|
||||
loader.QueueGuestProgram(guestShaders, specState, programIndex, isCompute);
|
||||
}
|
||||
|
||||
loader.CheckCompilation();
|
||||
@ -393,9 +417,17 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="tocFileStream">Host TOC file stream, intialized if needed</param>
|
||||
/// <param name="dataFileStream">Host data file stream, initialized if needed</param>
|
||||
/// <param name="guestShaders">Guest shader code for each active stage</param>
|
||||
/// <param name="programIndex">Index of the program on the cache</param>
|
||||
/// <param name="expectedTimestamp">Timestamp of the shared cache file. The host file must be newer than it</param>
|
||||
/// <returns>Host binary code, or null if not found</returns>
|
||||
private byte[] ReadHostCode(GpuContext context, ref Stream tocFileStream, ref Stream dataFileStream, int programIndex)
|
||||
private (byte[], CachedShaderStage[]) ReadHostCode(
|
||||
GpuContext context,
|
||||
ref Stream tocFileStream,
|
||||
ref Stream dataFileStream,
|
||||
GuestCodeAndCbData?[] guestShaders,
|
||||
int programIndex,
|
||||
ulong expectedTimestamp)
|
||||
{
|
||||
if (tocFileStream == null && dataFileStream == null)
|
||||
{
|
||||
@ -404,17 +436,28 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
|
||||
if (!File.Exists(tocFilePath) || !File.Exists(dataFilePath))
|
||||
{
|
||||
return null;
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
tocFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostTocFileName(context), writable: false);
|
||||
dataFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostDataFileName(context), writable: false);
|
||||
|
||||
BinarySerializer tempTocReader = new BinarySerializer(tocFileStream);
|
||||
|
||||
TocHeader header = new TocHeader();
|
||||
|
||||
tempTocReader.Read(ref header);
|
||||
|
||||
if (header.Timestamp < expectedTimestamp)
|
||||
{
|
||||
return (null, null);
|
||||
}
|
||||
}
|
||||
|
||||
int offset = Unsafe.SizeOf<TocHeader>() + programIndex * Unsafe.SizeOf<OffsetAndSize>();
|
||||
if (offset + Unsafe.SizeOf<OffsetAndSize>() > tocFileStream.Length)
|
||||
{
|
||||
return null;
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
if ((ulong)offset >= (ulong)dataFileStream.Length)
|
||||
@ -436,11 +479,33 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
|
||||
dataFileStream.Seek((long)offsetAndSize.Offset, SeekOrigin.Begin);
|
||||
|
||||
byte[] hostCode = new byte[offsetAndSize.Size];
|
||||
byte[] hostCode = new byte[offsetAndSize.UncompressedSize];
|
||||
|
||||
BinarySerializer.ReadCompressed(dataFileStream, hostCode);
|
||||
|
||||
return hostCode;
|
||||
CachedShaderStage[] shaders = new CachedShaderStage[guestShaders.Length];
|
||||
BinarySerializer dataReader = new BinarySerializer(dataFileStream);
|
||||
|
||||
dataFileStream.Seek((long)(offsetAndSize.Offset + offsetAndSize.CompressedSize), SeekOrigin.Begin);
|
||||
|
||||
dataReader.BeginCompression();
|
||||
|
||||
for (int index = 0; index < guestShaders.Length; index++)
|
||||
{
|
||||
if (!guestShaders[index].HasValue)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
GuestCodeAndCbData guestShader = guestShaders[index].Value;
|
||||
ShaderProgramInfo info = index != 0 || guestShaders.Length == 1 ? ReadShaderProgramInfo(ref dataReader) : null;
|
||||
|
||||
shaders[index] = new CachedShaderStage(info, guestShader.Code, guestShader.Cb1Data);
|
||||
}
|
||||
|
||||
dataReader.EndCompression();
|
||||
|
||||
return (hostCode, shaders);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -484,10 +549,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
var tocFileStream = streams != null ? streams.TocFileStream : DiskCacheCommon.OpenFile(_basePath, SharedTocFileName, writable: true);
|
||||
var dataFileStream = streams != null ? streams.DataFileStream : DiskCacheCommon.OpenFile(_basePath, SharedDataFileName, writable: true);
|
||||
|
||||
ulong timestamp = (ulong)DateTime.UtcNow.Subtract(DateTime.UnixEpoch).TotalSeconds;
|
||||
|
||||
if (tocFileStream.Length == 0)
|
||||
{
|
||||
TocHeader header = new TocHeader();
|
||||
CreateToc(tocFileStream, ref header, TocsMagic, CodeGenVersion);
|
||||
CreateToc(tocFileStream, ref header, TocsMagic, CodeGenVersion, timestamp);
|
||||
}
|
||||
|
||||
tocFileStream.Seek(0, SeekOrigin.End);
|
||||
@ -519,8 +586,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
stageEntry.GuestCodeIndex = _guestStorage.AddShader(shader.Code, shader.Cb1Data);
|
||||
|
||||
dataWriter.Write(ref stageEntry);
|
||||
|
||||
WriteShaderProgramInfo(ref dataWriter, shader.Info);
|
||||
}
|
||||
|
||||
program.SpecializationState.Write(ref dataWriter);
|
||||
@ -537,7 +602,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
return;
|
||||
}
|
||||
|
||||
WriteHostCode(context, hostCode, -1, streams);
|
||||
WriteHostCode(context, hostCode, program.Shaders, streams, timestamp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -574,29 +639,20 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
dataFileStream.SetLength(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a host binary shader to the host cache.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This only modifies the host cache. The shader must already exist in the other caches.
|
||||
/// This method should only be used for rebuilding the host cache after a clear.
|
||||
/// </remarks>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="hostCode">Host binary code</param>
|
||||
/// <param name="programIndex">Index of the program in the cache</param>
|
||||
public void AddHostShader(GpuContext context, ReadOnlySpan<byte> hostCode, int programIndex)
|
||||
{
|
||||
WriteHostCode(context, hostCode, programIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the host binary code on the host cache.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="hostCode">Host binary code</param>
|
||||
/// <param name="programIndex">Index of the program in the cache</param>
|
||||
/// <param name="shaders">Shader stages to be added to the host cache</param>
|
||||
/// <param name="streams">Output streams to use</param>
|
||||
private void WriteHostCode(GpuContext context, ReadOnlySpan<byte> hostCode, int programIndex, DiskCacheOutputStreams streams = null)
|
||||
/// <param name="timestamp">File creation timestamp</param>
|
||||
private void WriteHostCode(
|
||||
GpuContext context,
|
||||
ReadOnlySpan<byte> hostCode,
|
||||
CachedShaderStage[] shaders,
|
||||
DiskCacheOutputStreams streams,
|
||||
ulong timestamp)
|
||||
{
|
||||
var tocFileStream = streams != null ? streams.HostTocFileStream : DiskCacheCommon.OpenFile(_basePath, GetHostTocFileName(context), writable: true);
|
||||
var dataFileStream = streams != null ? streams.HostDataFileStream : DiskCacheCommon.OpenFile(_basePath, GetHostDataFileName(context), writable: true);
|
||||
@ -604,29 +660,39 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
if (tocFileStream.Length == 0)
|
||||
{
|
||||
TocHeader header = new TocHeader();
|
||||
CreateToc(tocFileStream, ref header, TochMagic, 0);
|
||||
}
|
||||
|
||||
if (programIndex == -1)
|
||||
{
|
||||
tocFileStream.Seek(0, SeekOrigin.End);
|
||||
}
|
||||
else
|
||||
{
|
||||
tocFileStream.Seek(Unsafe.SizeOf<TocHeader>() + (programIndex * Unsafe.SizeOf<OffsetAndSize>()), SeekOrigin.Begin);
|
||||
CreateToc(tocFileStream, ref header, TochMagic, 0, timestamp);
|
||||
}
|
||||
|
||||
tocFileStream.Seek(0, SeekOrigin.End);
|
||||
dataFileStream.Seek(0, SeekOrigin.End);
|
||||
|
||||
BinarySerializer tocWriter = new BinarySerializer(tocFileStream);
|
||||
BinarySerializer dataWriter = new BinarySerializer(dataFileStream);
|
||||
|
||||
OffsetAndSize offsetAndSize = new OffsetAndSize();
|
||||
offsetAndSize.Offset = (ulong)dataFileStream.Position;
|
||||
offsetAndSize.Size = (uint)hostCode.Length;
|
||||
tocWriter.Write(ref offsetAndSize);
|
||||
offsetAndSize.UncompressedSize = (uint)hostCode.Length;
|
||||
|
||||
long dataStartPosition = dataFileStream.Position;
|
||||
|
||||
BinarySerializer.WriteCompressed(dataFileStream, hostCode, DiskCacheCommon.GetCompressionAlgorithm());
|
||||
|
||||
offsetAndSize.CompressedSize = (uint)(dataFileStream.Position - dataStartPosition);
|
||||
|
||||
tocWriter.Write(ref offsetAndSize);
|
||||
|
||||
dataWriter.BeginCompression(DiskCacheCommon.GetCompressionAlgorithm());
|
||||
|
||||
for (int index = 0; index < shaders.Length; index++)
|
||||
{
|
||||
if (shaders[index] != null)
|
||||
{
|
||||
WriteShaderProgramInfo(ref dataWriter, shaders[index].Info);
|
||||
}
|
||||
}
|
||||
|
||||
dataWriter.EndCompression();
|
||||
|
||||
if (streams == null)
|
||||
{
|
||||
tocFileStream.Dispose();
|
||||
@ -641,7 +707,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
/// <param name="header">Set to the TOC file header</param>
|
||||
/// <param name="magic">Magic value to be written</param>
|
||||
/// <param name="codegenVersion">Shader codegen version, only valid for the host file</param>
|
||||
private void CreateToc(Stream tocFileStream, ref TocHeader header, uint magic, uint codegenVersion)
|
||||
/// <param name="timestamp">File creation timestamp</param>
|
||||
private void CreateToc(Stream tocFileStream, ref TocHeader header, uint magic, uint codegenVersion, ulong timestamp)
|
||||
{
|
||||
BinarySerializer writer = new BinarySerializer(tocFileStream);
|
||||
|
||||
@ -650,7 +717,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
header.CodeGenVersion = codegenVersion;
|
||||
header.Padding = 0;
|
||||
header.Reserved = 0;
|
||||
header.Reserved2 = 0;
|
||||
header.Timestamp = timestamp;
|
||||
|
||||
if (tocFileStream.Length > 0)
|
||||
{
|
||||
|
31
Ryujinx.Graphics.Gpu/Shader/DiskCache/GuestCodeAndCbData.cs
Normal file
31
Ryujinx.Graphics.Gpu/Shader/DiskCache/GuestCodeAndCbData.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using Ryujinx.Graphics.Shader;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
{
|
||||
/// <summary>
|
||||
/// Guest shader code and constant buffer data accessed by the shader.
|
||||
/// </summary>
|
||||
struct GuestCodeAndCbData
|
||||
{
|
||||
/// <summary>
|
||||
/// Maxwell binary shader code.
|
||||
/// </summary>
|
||||
public byte[] Code { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constant buffer 1 data accessed by the shader.
|
||||
/// </summary>
|
||||
public byte[] Cb1Data { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the guest shader code and constant buffer data.
|
||||
/// </summary>
|
||||
/// <param name="code">Maxwell binary shader code</param>
|
||||
/// <param name="cb1Data">Constant buffer 1 data accessed by the shader</param>
|
||||
public GuestCodeAndCbData(byte[] code, byte[] cb1Data)
|
||||
{
|
||||
Code = code;
|
||||
Cb1Data = cb1Data;
|
||||
}
|
||||
}
|
||||
}
|
@ -45,9 +45,9 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
public readonly CachedShaderProgram CachedProgram;
|
||||
|
||||
/// <summary>
|
||||
/// Host program.
|
||||
/// Optional binary code. If not null, it is used instead of the backend host binary.
|
||||
/// </summary>
|
||||
public readonly IProgram HostProgram;
|
||||
public readonly byte[] BinaryCode;
|
||||
|
||||
/// <summary>
|
||||
/// Program index.
|
||||
@ -68,19 +68,19 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
/// Creates a new program validation entry.
|
||||
/// </summary>
|
||||
/// <param name="cachedProgram">Cached shader program</param>
|
||||
/// <param name="hostProgram">Host program</param>
|
||||
/// <param name="binaryCode">Optional binary code. If not null, it is used instead of the backend host binary</param>
|
||||
/// <param name="programIndex">Program index</param>
|
||||
/// <param name="isCompute">Indicates if the program is a compute shader</param>
|
||||
/// <param name="isBinary">Indicates if the program is a host binary shader</param>
|
||||
public ProgramEntry(
|
||||
CachedShaderProgram cachedProgram,
|
||||
IProgram hostProgram,
|
||||
byte[] binaryCode,
|
||||
int programIndex,
|
||||
bool isCompute,
|
||||
bool isBinary)
|
||||
{
|
||||
CachedProgram = cachedProgram;
|
||||
HostProgram = hostProgram;
|
||||
BinaryCode = binaryCode;
|
||||
ProgramIndex = programIndex;
|
||||
IsCompute = isCompute;
|
||||
IsBinary = isBinary;
|
||||
@ -146,9 +146,9 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
private struct AsyncProgramTranslation
|
||||
{
|
||||
/// <summary>
|
||||
/// Cached shader stages.
|
||||
/// Guest code for each active stage.
|
||||
/// </summary>
|
||||
public readonly CachedShaderStage[] Shaders;
|
||||
public readonly GuestCodeAndCbData?[] GuestShaders;
|
||||
|
||||
/// <summary>
|
||||
/// Specialization state.
|
||||
@ -168,17 +168,17 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
/// <summary>
|
||||
/// Creates a new program translation entry.
|
||||
/// </summary>
|
||||
/// <param name="shaders">Cached shader stages</param>
|
||||
/// <param name="guestShaders">Guest code for each active stage</param>
|
||||
/// <param name="specState">Specialization state</param>
|
||||
/// <param name="programIndex">Program index</param>
|
||||
/// <param name="isCompute">Indicates if the program is a compute shader</param>
|
||||
public AsyncProgramTranslation(
|
||||
CachedShaderStage[] shaders,
|
||||
GuestCodeAndCbData?[] guestShaders,
|
||||
ShaderSpecializationState specState,
|
||||
int programIndex,
|
||||
bool isCompute)
|
||||
{
|
||||
Shaders = shaders;
|
||||
GuestShaders = guestShaders;
|
||||
SpecializationState = specState;
|
||||
ProgramIndex = programIndex;
|
||||
IsCompute = isCompute;
|
||||
@ -188,7 +188,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
private readonly Queue<ProgramEntry> _validationQueue;
|
||||
private readonly ConcurrentQueue<ProgramCompilation> _compilationQueue;
|
||||
private readonly BlockingCollection<AsyncProgramTranslation> _asyncTranslationQueue;
|
||||
private readonly SortedList<int, CachedShaderProgram> _programList;
|
||||
private readonly SortedList<int, (CachedShaderProgram, byte[])> _programList;
|
||||
|
||||
private int _backendParallelCompileThreads;
|
||||
private int _compiledCount;
|
||||
@ -220,7 +220,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
_validationQueue = new Queue<ProgramEntry>();
|
||||
_compilationQueue = new ConcurrentQueue<ProgramCompilation>();
|
||||
_asyncTranslationQueue = new BlockingCollection<AsyncProgramTranslation>(ThreadCount);
|
||||
_programList = new SortedList<int, CachedShaderProgram>();
|
||||
_programList = new SortedList<int, (CachedShaderProgram, byte[])>();
|
||||
_backendParallelCompileThreads = Math.Min(Environment.ProcessorCount, 8); // Must be kept in sync with the backend code.
|
||||
}
|
||||
|
||||
@ -235,7 +235,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
{
|
||||
workThreads[index] = new Thread(ProcessAsyncQueue)
|
||||
{
|
||||
Name = $"Gpu.AsyncTranslationThread.{index}"
|
||||
Name = $"GPU.AsyncTranslationThread.{index}"
|
||||
};
|
||||
}
|
||||
|
||||
@ -287,7 +287,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
|
||||
CheckCompilationBlocking();
|
||||
|
||||
if (_needsHostRegen)
|
||||
if (_needsHostRegen && Active)
|
||||
{
|
||||
// Rebuild both shared and host cache files.
|
||||
// Rebuilding shared is required because the shader information returned by the translator
|
||||
@ -310,8 +310,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
break;
|
||||
}
|
||||
|
||||
CachedShaderProgram program = kv.Value;
|
||||
_hostStorage.AddShader(_context, program, program.HostProgram.GetBinary(), streams);
|
||||
(CachedShaderProgram program, byte[] binaryCode) = kv.Value;
|
||||
_hostStorage.AddShader(_context, program, binaryCode, streams);
|
||||
}
|
||||
|
||||
Logger.Info?.Print(LogClass.Gpu, $"Rebuilt {_programList.Count} shaders successfully.");
|
||||
@ -342,24 +342,31 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
/// Enqueues a host program for compilation.
|
||||
/// </summary>
|
||||
/// <param name="cachedProgram">Cached program</param>
|
||||
/// <param name="hostProgram">Host program to be compiled</param>
|
||||
/// <param name="binaryCode">Host binary code</param>
|
||||
/// <param name="programIndex">Program index</param>
|
||||
/// <param name="isCompute">Indicates if the program is a compute shader</param>
|
||||
public void QueueHostProgram(CachedShaderProgram cachedProgram, IProgram hostProgram, int programIndex, bool isCompute)
|
||||
public void QueueHostProgram(CachedShaderProgram cachedProgram, byte[] binaryCode, int programIndex, bool isCompute)
|
||||
{
|
||||
EnqueueForValidation(new ProgramEntry(cachedProgram, hostProgram, programIndex, isCompute, isBinary: true));
|
||||
EnqueueForValidation(new ProgramEntry(cachedProgram, binaryCode, programIndex, isCompute, isBinary: true));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enqueues a guest program for compilation.
|
||||
/// </summary>
|
||||
/// <param name="shaders">Cached shader stages</param>
|
||||
/// <param name="guestShaders">Guest code for each active stage</param>
|
||||
/// <param name="specState">Specialization state</param>
|
||||
/// <param name="programIndex">Program index</param>
|
||||
/// <param name="isCompute">Indicates if the program is a compute shader</param>
|
||||
public void QueueGuestProgram(CachedShaderStage[] shaders, ShaderSpecializationState specState, int programIndex, bool isCompute)
|
||||
public void QueueGuestProgram(GuestCodeAndCbData?[] guestShaders, ShaderSpecializationState specState, int programIndex, bool isCompute)
|
||||
{
|
||||
_asyncTranslationQueue.Add(new AsyncProgramTranslation(shaders, specState, programIndex, isCompute));
|
||||
try
|
||||
{
|
||||
AsyncProgramTranslation asyncTranslation = new AsyncProgramTranslation(guestShaders, specState, programIndex, isCompute);
|
||||
_asyncTranslationQueue.Add(asyncTranslation, _cancellationToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -374,7 +381,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
// If not yet compiled, do nothing. This avoids blocking to wait for shader compilation.
|
||||
while (_validationQueue.TryPeek(out ProgramEntry entry))
|
||||
{
|
||||
ProgramLinkStatus result = entry.HostProgram.CheckProgramLink(false);
|
||||
ProgramLinkStatus result = entry.CachedProgram.HostProgram.CheckProgramLink(false);
|
||||
|
||||
if (result != ProgramLinkStatus.Incomplete)
|
||||
{
|
||||
@ -398,7 +405,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
|
||||
while (_validationQueue.TryDequeue(out ProgramEntry entry) && Active)
|
||||
{
|
||||
ProcessCompiledProgram(ref entry, entry.HostProgram.CheckProgramLink(true), asyncCompile: false);
|
||||
ProcessCompiledProgram(ref entry, entry.CachedProgram.HostProgram.CheckProgramLink(true), asyncCompile: false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -427,7 +434,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
_needsHostRegen = true;
|
||||
}
|
||||
|
||||
_programList.Add(entry.ProgramIndex, entry.CachedProgram);
|
||||
_programList.Add(entry.ProgramIndex, (entry.CachedProgram, entry.BinaryCode));
|
||||
SignalCompiled();
|
||||
}
|
||||
else if (entry.IsBinary)
|
||||
@ -436,13 +443,25 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
// we still have a chance to recompile from the guest binary.
|
||||
CachedShaderProgram program = entry.CachedProgram;
|
||||
|
||||
GuestCodeAndCbData?[] guestShaders = new GuestCodeAndCbData?[program.Shaders.Length];
|
||||
|
||||
for (int index = 0; index < program.Shaders.Length; index++)
|
||||
{
|
||||
CachedShaderStage shader = program.Shaders[index];
|
||||
|
||||
if (shader != null)
|
||||
{
|
||||
guestShaders[index] = new GuestCodeAndCbData(shader.Code, shader.Cb1Data);
|
||||
}
|
||||
}
|
||||
|
||||
if (asyncCompile)
|
||||
{
|
||||
QueueGuestProgram(program.Shaders, program.SpecializationState, entry.ProgramIndex, entry.IsCompute);
|
||||
QueueGuestProgram(guestShaders, program.SpecializationState, entry.ProgramIndex, entry.IsCompute);
|
||||
}
|
||||
else
|
||||
{
|
||||
RecompileFromGuestCode(program.Shaders, program.SpecializationState, entry.ProgramIndex, entry.IsCompute);
|
||||
RecompileFromGuestCode(guestShaders, program.SpecializationState, entry.ProgramIndex, entry.IsCompute);
|
||||
ProcessCompilationQueue();
|
||||
}
|
||||
}
|
||||
@ -476,10 +495,16 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
}
|
||||
}
|
||||
|
||||
IProgram hostProgram = _context.Renderer.CreateProgram(shaderSources, new ShaderInfo(fragmentOutputMap));
|
||||
ShaderInfo shaderInfo = compilation.SpecializationState.PipelineState.HasValue
|
||||
? new ShaderInfo(fragmentOutputMap, compilation.SpecializationState.PipelineState.Value, fromCache: true)
|
||||
: new ShaderInfo(fragmentOutputMap, fromCache: true);
|
||||
|
||||
IProgram hostProgram = _context.Renderer.CreateProgram(shaderSources, shaderInfo);
|
||||
CachedShaderProgram program = new CachedShaderProgram(hostProgram, compilation.SpecializationState, compilation.Shaders);
|
||||
|
||||
EnqueueForValidation(new ProgramEntry(program, hostProgram, compilation.ProgramIndex, compilation.IsCompute, isBinary: false));
|
||||
byte[] binaryCode = _context.Capabilities.Api == TargetApi.Vulkan ? ShaderBinarySerializer.Pack(shaderSources) : hostProgram.GetBinary();
|
||||
|
||||
EnqueueForValidation(new ProgramEntry(program, binaryCode, compilation.ProgramIndex, compilation.IsCompute, isBinary: false));
|
||||
}
|
||||
}
|
||||
|
||||
@ -496,7 +521,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
// Submitting more seems to cause NVIDIA OpenGL driver to crash.
|
||||
if (_validationQueue.Count >= _backendParallelCompileThreads && _validationQueue.TryDequeue(out ProgramEntry entry))
|
||||
{
|
||||
ProcessCompiledProgram(ref entry, entry.HostProgram.CheckProgramLink(true), asyncCompile: false);
|
||||
ProcessCompiledProgram(ref entry, entry.CachedProgram.HostProgram.CheckProgramLink(true), asyncCompile: false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -513,7 +538,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
foreach (AsyncProgramTranslation asyncCompilation in _asyncTranslationQueue.GetConsumingEnumerable(ct))
|
||||
{
|
||||
RecompileFromGuestCode(
|
||||
asyncCompilation.Shaders,
|
||||
asyncCompilation.GuestShaders,
|
||||
asyncCompilation.SpecializationState,
|
||||
asyncCompilation.ProgramIndex,
|
||||
asyncCompilation.IsCompute);
|
||||
@ -527,21 +552,21 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
/// <summary>
|
||||
/// Recompiles a program from guest code.
|
||||
/// </summary>
|
||||
/// <param name="shaders">Shader stages</param>
|
||||
/// <param name="guestShaders">Guest code for each active stage</param>
|
||||
/// <param name="specState">Specialization state</param>
|
||||
/// <param name="programIndex">Program index</param>
|
||||
/// <param name="isCompute">Indicates if the program is a compute shader</param>
|
||||
private void RecompileFromGuestCode(CachedShaderStage[] shaders, ShaderSpecializationState specState, int programIndex, bool isCompute)
|
||||
private void RecompileFromGuestCode(GuestCodeAndCbData?[] guestShaders, ShaderSpecializationState specState, int programIndex, bool isCompute)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (isCompute)
|
||||
{
|
||||
RecompileComputeFromGuestCode(shaders, specState, programIndex);
|
||||
RecompileComputeFromGuestCode(guestShaders, specState, programIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
RecompileGraphicsFromGuestCode(shaders, specState, programIndex);
|
||||
RecompileGraphicsFromGuestCode(guestShaders, specState, programIndex);
|
||||
}
|
||||
}
|
||||
catch (DiskCacheLoadException diskCacheLoadException)
|
||||
@ -556,41 +581,47 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
/// <summary>
|
||||
/// Recompiles a graphics program from guest code.
|
||||
/// </summary>
|
||||
/// <param name="shaders">Shader stages</param>
|
||||
/// <param name="guestShaders">Guest code for each active stage</param>
|
||||
/// <param name="specState">Specialization state</param>
|
||||
/// <param name="programIndex">Program index</param>
|
||||
private void RecompileGraphicsFromGuestCode(CachedShaderStage[] shaders, ShaderSpecializationState specState, int programIndex)
|
||||
private void RecompileGraphicsFromGuestCode(GuestCodeAndCbData?[] guestShaders, ShaderSpecializationState specState, int programIndex)
|
||||
{
|
||||
ShaderSpecializationState newSpecState = new ShaderSpecializationState(specState.GraphicsState, specState.TransformFeedbackDescriptors);
|
||||
ShaderSpecializationState newSpecState = new ShaderSpecializationState(
|
||||
ref specState.GraphicsState,
|
||||
specState.PipelineState,
|
||||
specState.TransformFeedbackDescriptors);
|
||||
|
||||
ResourceCounts counts = new ResourceCounts();
|
||||
|
||||
TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1];
|
||||
TranslatorContext nextStage = null;
|
||||
|
||||
TargetApi api = _context.Capabilities.Api;
|
||||
|
||||
for (int stageIndex = Constants.ShaderStages - 1; stageIndex >= 0; stageIndex--)
|
||||
{
|
||||
CachedShaderStage shader = shaders[stageIndex + 1];
|
||||
|
||||
if (shader != null)
|
||||
if (guestShaders[stageIndex + 1].HasValue)
|
||||
{
|
||||
GuestCodeAndCbData shader = guestShaders[stageIndex + 1].Value;
|
||||
|
||||
byte[] guestCode = shader.Code;
|
||||
byte[] cb1Data = shader.Cb1Data;
|
||||
|
||||
DiskCacheGpuAccessor gpuAccessor = new DiskCacheGpuAccessor(_context, guestCode, cb1Data, specState, newSpecState, counts, stageIndex);
|
||||
TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, DefaultFlags, 0);
|
||||
TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags, 0);
|
||||
|
||||
if (nextStage != null)
|
||||
{
|
||||
currentStage.SetNextStage(nextStage);
|
||||
}
|
||||
|
||||
if (stageIndex == 0 && shaders[0] != null)
|
||||
if (stageIndex == 0 && guestShaders[0].HasValue)
|
||||
{
|
||||
byte[] guestCodeA = shaders[0].Code;
|
||||
byte[] cb1DataA = shaders[0].Cb1Data;
|
||||
byte[] guestCodeA = guestShaders[0].Value.Code;
|
||||
byte[] cb1DataA = guestShaders[0].Value.Cb1Data;
|
||||
|
||||
DiskCacheGpuAccessor gpuAccessorA = new DiskCacheGpuAccessor(_context, guestCodeA, cb1DataA, specState, newSpecState, counts, 0);
|
||||
translatorContexts[0] = DecodeGraphicsShader(gpuAccessorA, DefaultFlags | TranslationFlags.VertexA, 0);
|
||||
translatorContexts[0] = DecodeGraphicsShader(gpuAccessorA, api, DefaultFlags | TranslationFlags.VertexA, 0);
|
||||
}
|
||||
|
||||
translatorContexts[stageIndex + 1] = currentStage;
|
||||
@ -598,6 +629,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
}
|
||||
}
|
||||
|
||||
CachedShaderStage[] shaders = new CachedShaderStage[guestShaders.Length];
|
||||
List<ShaderProgram> translatedStages = new List<ShaderProgram>();
|
||||
|
||||
for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++)
|
||||
@ -608,15 +640,15 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
{
|
||||
ShaderProgram program;
|
||||
|
||||
byte[] guestCode = shaders[stageIndex + 1].Code;
|
||||
byte[] cb1Data = shaders[stageIndex + 1].Cb1Data;
|
||||
byte[] guestCode = guestShaders[stageIndex + 1].Value.Code;
|
||||
byte[] cb1Data = guestShaders[stageIndex + 1].Value.Cb1Data;
|
||||
|
||||
if (stageIndex == 0 && shaders[0] != null)
|
||||
if (stageIndex == 0 && guestShaders[0].HasValue)
|
||||
{
|
||||
program = currentStage.Translate(translatorContexts[0]);
|
||||
|
||||
byte[] guestCodeA = shaders[0].Code;
|
||||
byte[] cb1DataA = shaders[0].Cb1Data;
|
||||
byte[] guestCodeA = guestShaders[0].Value.Code;
|
||||
byte[] cb1DataA = guestShaders[0].Value.Cb1Data;
|
||||
|
||||
shaders[0] = new CachedShaderStage(null, guestCodeA, cb1DataA);
|
||||
shaders[1] = new CachedShaderStage(program.Info, guestCode, cb1Data);
|
||||
@ -641,21 +673,21 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
/// <summary>
|
||||
/// Recompiles a compute program from guest code.
|
||||
/// </summary>
|
||||
/// <param name="shaders">Shader stages</param>
|
||||
/// <param name="guestShaders">Guest code for each active stage</param>
|
||||
/// <param name="specState">Specialization state</param>
|
||||
/// <param name="programIndex">Program index</param>
|
||||
private void RecompileComputeFromGuestCode(CachedShaderStage[] shaders, ShaderSpecializationState specState, int programIndex)
|
||||
private void RecompileComputeFromGuestCode(GuestCodeAndCbData?[] guestShaders, ShaderSpecializationState specState, int programIndex)
|
||||
{
|
||||
CachedShaderStage shader = shaders[0];
|
||||
GuestCodeAndCbData shader = guestShaders[0].Value;
|
||||
ResourceCounts counts = new ResourceCounts();
|
||||
ShaderSpecializationState newSpecState = new ShaderSpecializationState(specState.ComputeState);
|
||||
ShaderSpecializationState newSpecState = new ShaderSpecializationState(ref specState.ComputeState);
|
||||
DiskCacheGpuAccessor gpuAccessor = new DiskCacheGpuAccessor(_context, shader.Code, shader.Cb1Data, specState, newSpecState, counts, 0);
|
||||
|
||||
TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, 0);
|
||||
TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, 0);
|
||||
|
||||
ShaderProgram program = translatorContext.Translate();
|
||||
|
||||
shaders[0] = new CachedShaderStage(program.Info, shader.Code, shader.Cb1Data);
|
||||
CachedShaderStage[] shaders = new[] { new CachedShaderStage(program.Info, shader.Code, shader.Cb1Data) };
|
||||
|
||||
_compilationQueue.Enqueue(new ProgramCompilation(new[] { program }, shaders, newSpecState, programIndex, isCompute: true));
|
||||
}
|
||||
|
@ -0,0 +1,49 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
{
|
||||
static class ShaderBinarySerializer
|
||||
{
|
||||
public static byte[] Pack(ShaderSource[] sources)
|
||||
{
|
||||
using MemoryStream output = new MemoryStream();
|
||||
using BinaryWriter writer = new BinaryWriter(output);
|
||||
|
||||
for (int i = 0; i < sources.Length; i++)
|
||||
{
|
||||
writer.Write(sources[i].BinaryCode.Length);
|
||||
writer.Write(sources[i].BinaryCode);
|
||||
}
|
||||
|
||||
return output.ToArray();
|
||||
}
|
||||
|
||||
public static ShaderSource[] Unpack(CachedShaderStage[] stages, byte[] code, bool compute)
|
||||
{
|
||||
using MemoryStream input = new MemoryStream(code);
|
||||
using BinaryReader reader = new BinaryReader(input);
|
||||
|
||||
List<ShaderSource> output = new List<ShaderSource>();
|
||||
|
||||
for (int i = compute ? 0 : 1; i < stages.Length; i++)
|
||||
{
|
||||
CachedShaderStage stage = stages[i];
|
||||
|
||||
if (stage == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int binaryCodeLength = reader.ReadInt32();
|
||||
byte[] binaryCode = reader.ReadBytes(binaryCodeLength);
|
||||
|
||||
output.Add(new ShaderSource(binaryCode, ShaderCache.GetBindings(stage.Info), stage.Info.Stage, TargetLanguage.Spirv));
|
||||
}
|
||||
|
||||
return output.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
@ -15,6 +17,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
private readonly GpuAccessorState _state;
|
||||
private readonly int _stageIndex;
|
||||
private readonly bool _compute;
|
||||
private readonly bool _isVulkan;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the GPU state accessor for graphics shader translation.
|
||||
@ -23,8 +26,13 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="state">Current GPU state</param>
|
||||
/// <param name="stageIndex">Graphics shader stage index (0 = Vertex, 4 = Fragment)</param>
|
||||
public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state, int stageIndex) : base(context)
|
||||
public GpuAccessor(
|
||||
GpuContext context,
|
||||
GpuChannel channel,
|
||||
GpuAccessorState state,
|
||||
int stageIndex) : base(context, state.ResourceCounts, stageIndex)
|
||||
{
|
||||
_isVulkan = context.Capabilities.Api == TargetApi.Vulkan;
|
||||
_channel = channel;
|
||||
_state = state;
|
||||
_stageIndex = stageIndex;
|
||||
@ -36,7 +44,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="state">Current GPU state</param>
|
||||
public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state) : base(context)
|
||||
public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state) : base(context, state.ResourceCounts, 0)
|
||||
{
|
||||
_channel = channel;
|
||||
_state = state;
|
||||
@ -73,27 +81,36 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int QueryBindingConstantBuffer(int index)
|
||||
public AlphaTestOp QueryAlphaTestCompare()
|
||||
{
|
||||
return _state.ResourceCounts.UniformBuffersCount++;
|
||||
if (!_isVulkan || !_state.GraphicsState.AlphaTestEnable)
|
||||
{
|
||||
return AlphaTestOp.Always;
|
||||
}
|
||||
|
||||
return _state.GraphicsState.AlphaTestCompare switch
|
||||
{
|
||||
CompareOp.Never or CompareOp.NeverGl => AlphaTestOp.Never,
|
||||
CompareOp.Less or CompareOp.LessGl => AlphaTestOp.Less,
|
||||
CompareOp.Equal or CompareOp.EqualGl => AlphaTestOp.Equal,
|
||||
CompareOp.LessOrEqual or CompareOp.LessOrEqualGl => AlphaTestOp.LessOrEqual,
|
||||
CompareOp.Greater or CompareOp.GreaterGl => AlphaTestOp.Greater,
|
||||
CompareOp.NotEqual or CompareOp.NotEqualGl => AlphaTestOp.NotEqual,
|
||||
CompareOp.GreaterOrEqual or CompareOp.GreaterOrEqualGl => AlphaTestOp.GreaterOrEqual,
|
||||
_ => AlphaTestOp.Always
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int QueryBindingStorageBuffer(int index)
|
||||
public float QueryAlphaTestReference()
|
||||
{
|
||||
return _state.ResourceCounts.StorageBuffersCount++;
|
||||
return _state.GraphicsState.AlphaTestReference;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int QueryBindingTexture(int index)
|
||||
public AttributeType QueryAttributeType(int location)
|
||||
{
|
||||
return _state.ResourceCounts.TexturesCount++;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int QueryBindingImage(int index)
|
||||
{
|
||||
return _state.ResourceCounts.ImagesCount++;
|
||||
return _state.GraphicsState.AttributeTypes[location];
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@ -129,6 +146,18 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
return ConvertToInputTopology(_state.GraphicsState.Topology, _state.GraphicsState.TessellationMode);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool QueryProgramPointSize()
|
||||
{
|
||||
return _state.GraphicsState.ProgramPointSizeEnable;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float QueryPointSize()
|
||||
{
|
||||
return _state.GraphicsState.PointSize;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool QueryTessCw()
|
||||
{
|
||||
@ -198,6 +227,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool QueryTransformDepthMinusOneToOne()
|
||||
{
|
||||
return _state.GraphicsState.DepthMode;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool QueryTransformFeedbackEnabled()
|
||||
{
|
||||
|
@ -1,7 +1,9 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Threed;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
@ -11,74 +13,140 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
class GpuAccessorBase
|
||||
{
|
||||
private readonly GpuContext _context;
|
||||
private readonly ResourceCounts _resourceCounts;
|
||||
private readonly int _stageIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new GPU accessor.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
public GpuAccessorBase(GpuContext context)
|
||||
public GpuAccessorBase(GpuContext context, ResourceCounts resourceCounts, int stageIndex)
|
||||
{
|
||||
_context = context;
|
||||
_resourceCounts = resourceCounts;
|
||||
_stageIndex = stageIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries host about the presence of the FrontFacing built-in variable bug.
|
||||
/// </summary>
|
||||
/// <returns>True if the bug is present on the host device used, false otherwise</returns>
|
||||
/// <inheritdoc/>
|
||||
public int QueryBindingConstantBuffer(int index)
|
||||
{
|
||||
if (_context.Capabilities.Api == TargetApi.Vulkan)
|
||||
{
|
||||
// We need to start counting from 1 since binding 0 is reserved for the support uniform buffer.
|
||||
return GetBindingFromIndex(index, _context.Capabilities.MaximumUniformBuffersPerStage, "Uniform buffer") + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return _resourceCounts.UniformBuffersCount++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int QueryBindingStorageBuffer(int index)
|
||||
{
|
||||
if (_context.Capabilities.Api == TargetApi.Vulkan)
|
||||
{
|
||||
return GetBindingFromIndex(index, _context.Capabilities.MaximumStorageBuffersPerStage, "Storage buffer");
|
||||
}
|
||||
else
|
||||
{
|
||||
return _resourceCounts.StorageBuffersCount++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int QueryBindingTexture(int index, bool isBuffer)
|
||||
{
|
||||
if (_context.Capabilities.Api == TargetApi.Vulkan)
|
||||
{
|
||||
if (isBuffer)
|
||||
{
|
||||
index += (int)_context.Capabilities.MaximumTexturesPerStage;
|
||||
}
|
||||
|
||||
return GetBindingFromIndex(index, _context.Capabilities.MaximumTexturesPerStage * 2, "Texture");
|
||||
}
|
||||
else
|
||||
{
|
||||
return _resourceCounts.TexturesCount++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int QueryBindingImage(int index, bool isBuffer)
|
||||
{
|
||||
if (_context.Capabilities.Api == TargetApi.Vulkan)
|
||||
{
|
||||
if (isBuffer)
|
||||
{
|
||||
index += (int)_context.Capabilities.MaximumImagesPerStage;
|
||||
}
|
||||
|
||||
return GetBindingFromIndex(index, _context.Capabilities.MaximumImagesPerStage * 2, "Image");
|
||||
}
|
||||
else
|
||||
{
|
||||
return _resourceCounts.ImagesCount++;
|
||||
}
|
||||
}
|
||||
|
||||
private int GetBindingFromIndex(int index, uint maxPerStage, string resourceName)
|
||||
{
|
||||
if ((uint)index >= maxPerStage)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Gpu, $"{resourceName} index {index} exceeds per stage limit of {maxPerStage}.");
|
||||
}
|
||||
|
||||
return GetStageIndex() * (int)maxPerStage + index;
|
||||
}
|
||||
|
||||
private int GetStageIndex()
|
||||
{
|
||||
// This is just a simple remapping to ensure that most frequently used shader stages
|
||||
// have the lowest binding numbers.
|
||||
// This is useful because if we need to run on a system with a low limit on the bindings,
|
||||
// then we can still get most games working as the most common shaders will have low binding numbers.
|
||||
return _stageIndex switch
|
||||
{
|
||||
4 => 1, // Fragment
|
||||
3 => 2, // Geometry
|
||||
1 => 3, // Tessellation control
|
||||
2 => 4, // Tessellation evaluation
|
||||
_ => 0 // Vertex/Compute
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool QueryHostHasFrontFacingBug() => _context.Capabilities.HasFrontFacingBug;
|
||||
|
||||
/// <summary>
|
||||
/// Queries host about the presence of the vector indexing bug.
|
||||
/// </summary>
|
||||
/// <returns>True if the bug is present on the host device used, false otherwise</returns>
|
||||
/// <inheritdoc/>
|
||||
public bool QueryHostHasVectorIndexingBug() => _context.Capabilities.HasVectorIndexingBug;
|
||||
|
||||
/// <summary>
|
||||
/// Queries host storage buffer alignment required.
|
||||
/// </summary>
|
||||
/// <returns>Host storage buffer alignment in bytes</returns>
|
||||
/// <inheritdoc/>
|
||||
public int QueryHostStorageBufferOffsetAlignment() => _context.Capabilities.StorageBufferOffsetAlignment;
|
||||
|
||||
/// <summary>
|
||||
/// Queries host support for texture formats with BGRA component order (such as BGRA8).
|
||||
/// </summary>
|
||||
/// <returns>True if BGRA formats are supported, false otherwise</returns>
|
||||
/// <inheritdoc/>
|
||||
public bool QueryHostSupportsBgraFormat() => _context.Capabilities.SupportsBgraFormat;
|
||||
|
||||
/// <summary>
|
||||
/// Queries host support for fragment shader ordering critical sections on the shader code.
|
||||
/// </summary>
|
||||
/// <returns>True if fragment shader interlock is supported, false otherwise</returns>
|
||||
/// <inheritdoc/>
|
||||
public bool QueryHostSupportsFragmentShaderInterlock() => _context.Capabilities.SupportsFragmentShaderInterlock;
|
||||
|
||||
/// <summary>
|
||||
/// Queries host support for fragment shader ordering scoped critical sections on the shader code.
|
||||
/// </summary>
|
||||
/// <returns>True if fragment shader ordering is supported, false otherwise</returns>
|
||||
/// <inheritdoc/>
|
||||
public bool QueryHostSupportsFragmentShaderOrderingIntel() => _context.Capabilities.SupportsFragmentShaderOrderingIntel;
|
||||
|
||||
/// <summary>
|
||||
/// Queries host support for readable images without a explicit format declaration on the shader.
|
||||
/// </summary>
|
||||
/// <returns>True if formatted image load is supported, false otherwise</returns>
|
||||
/// <inheritdoc/>
|
||||
public bool QueryHostSupportsGeometryShaderPassthrough() => _context.Capabilities.SupportsGeometryShaderPassthrough;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool QueryHostSupportsImageLoadFormatted() => _context.Capabilities.SupportsImageLoadFormatted;
|
||||
|
||||
/// <summary>
|
||||
/// Queries host GPU non-constant texture offset support.
|
||||
/// </summary>
|
||||
/// <returns>True if the GPU and driver supports non-constant texture offsets, false otherwise</returns>
|
||||
/// <inheritdoc/>
|
||||
public bool QueryHostSupportsNonConstantTextureOffset() => _context.Capabilities.SupportsNonConstantTextureOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Queries host GPU shader ballot support.
|
||||
/// </summary>
|
||||
/// <returns>True if the GPU and driver supports shader ballot, false otherwise</returns>
|
||||
/// <inheritdoc/>
|
||||
public bool QueryHostSupportsShaderBallot() => _context.Capabilities.SupportsShaderBallot;
|
||||
|
||||
/// <summary>
|
||||
/// Queries host GPU texture shadow LOD support.
|
||||
/// </summary>
|
||||
/// <returns>True if the GPU and driver supports texture shadow LOD, false otherwise</returns>
|
||||
/// <inheritdoc/>
|
||||
public bool QueryHostSupportsTextureShadowLod() => _context.Capabilities.SupportsTextureShadowLod;
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,5 +1,7 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Threed;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
@ -26,43 +28,99 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
public readonly TessMode TessellationMode;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whenever the viewport transform is disabled.
|
||||
/// </summary>
|
||||
public readonly bool ViewportTransformDisable;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whenever alpha-to-coverage is enabled.
|
||||
/// Indicates whether alpha-to-coverage is enabled.
|
||||
/// </summary>
|
||||
public readonly bool AlphaToCoverageEnable;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whenever alpha-to-coverage dithering is enabled.
|
||||
/// Indicates whether alpha-to-coverage dithering is enabled.
|
||||
/// </summary>
|
||||
public readonly bool AlphaToCoverageDitherEnable;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the viewport transform is disabled.
|
||||
/// </summary>
|
||||
public readonly bool ViewportTransformDisable;
|
||||
|
||||
/// <summary>
|
||||
/// Depth mode zero to one or minus one to one.
|
||||
/// </summary>
|
||||
public readonly bool DepthMode;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the point size is set on the shader or is fixed.
|
||||
/// </summary>
|
||||
public readonly bool ProgramPointSizeEnable;
|
||||
|
||||
/// <summary>
|
||||
/// Point size used if <see cref="ProgramPointSizeEnable" /> is false.
|
||||
/// </summary>
|
||||
public readonly float PointSize;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether alpha test is enabled.
|
||||
/// </summary>
|
||||
public readonly bool AlphaTestEnable;
|
||||
|
||||
/// <summary>
|
||||
/// When alpha test is enabled, indicates the comparison that decides if the fragment should be discarded.
|
||||
/// </summary>
|
||||
public readonly CompareOp AlphaTestCompare;
|
||||
|
||||
/// <summary>
|
||||
/// When alpha test is enabled, indicates the value to compare with the fragment output alpha.
|
||||
/// </summary>
|
||||
public readonly float AlphaTestReference;
|
||||
|
||||
/// <summary>
|
||||
/// Type of the vertex attributes consumed by the shader.
|
||||
/// </summary>
|
||||
public Array32<AttributeType> AttributeTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new GPU graphics state.
|
||||
/// </summary>
|
||||
/// <param name="earlyZForce">Early Z force enable</param>
|
||||
/// <param name="topology">Primitive topology</param>
|
||||
/// <param name="tessellationMode">Tessellation mode</param>
|
||||
/// <param name="viewportTransformDisable">Indicates whenever the viewport transform is disabled</param>
|
||||
/// <param name="alphaToCoverageEnable">Indicates whenever alpha-to-coverage is enabled</param>
|
||||
/// <param name="alphaToCoverageDitherEnable">Indicates whenever alpha-to-coverage dithering is enabled</param>
|
||||
/// <param name="alphaToCoverageEnable">Indicates whether alpha-to-coverage is enabled</param>
|
||||
/// <param name="alphaToCoverageDitherEnable">Indicates whether alpha-to-coverage dithering is enabled</param>
|
||||
/// <param name="viewportTransformDisable">Indicates whether the viewport transform is disabled</param>
|
||||
/// <param name="depthMode">Depth mode zero to one or minus one to one</param>
|
||||
/// <param name="programPointSizeEnable">Indicates if the point size is set on the shader or is fixed</param>
|
||||
/// <param name="pointSize">Point size if not set from shader</param>
|
||||
/// <param name="alphaTestEnable">Indicates whether alpha test is enabled</param>
|
||||
/// <param name="alphaTestCompare">When alpha test is enabled, indicates the comparison that decides if the fragment should be discarded</param>
|
||||
/// <param name="alphaTestReference">When alpha test is enabled, indicates the value to compare with the fragment output alpha</param>
|
||||
/// <param name="attributeTypes">Type of the vertex attributes consumed by the shader</param>
|
||||
public GpuChannelGraphicsState(
|
||||
bool earlyZForce,
|
||||
PrimitiveTopology topology,
|
||||
TessMode tessellationMode,
|
||||
bool viewportTransformDisable,
|
||||
bool alphaToCoverageEnable,
|
||||
bool alphaToCoverageDitherEnable)
|
||||
bool alphaToCoverageDitherEnable,
|
||||
bool viewportTransformDisable,
|
||||
bool depthMode,
|
||||
bool programPointSizeEnable,
|
||||
float pointSize,
|
||||
bool alphaTestEnable,
|
||||
CompareOp alphaTestCompare,
|
||||
float alphaTestReference,
|
||||
ref Array32<AttributeType> attributeTypes)
|
||||
{
|
||||
EarlyZForce = earlyZForce;
|
||||
Topology = topology;
|
||||
TessellationMode = tessellationMode;
|
||||
ViewportTransformDisable = viewportTransformDisable;
|
||||
AlphaToCoverageEnable = alphaToCoverageEnable;
|
||||
AlphaToCoverageDitherEnable = alphaToCoverageDitherEnable;
|
||||
ViewportTransformDisable = viewportTransformDisable;
|
||||
DepthMode = depthMode;
|
||||
ProgramPointSizeEnable = programPointSizeEnable;
|
||||
PointSize = pointSize;
|
||||
AlphaTestEnable = alphaTestEnable;
|
||||
AlphaTestCompare = alphaTestCompare;
|
||||
AlphaTestReference = alphaTestReference;
|
||||
AttributeTypes = attributeTypes;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +1,17 @@
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Threed;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Gpu.Shader.Cache;
|
||||
using Ryujinx.Graphics.Gpu.Shader.DiskCache;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader
|
||||
@ -59,11 +63,13 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
public readonly CachedShaderProgram CachedProgram;
|
||||
public readonly IProgram HostProgram;
|
||||
public readonly byte[] BinaryCode;
|
||||
|
||||
public ProgramToSave(CachedShaderProgram cachedProgram, IProgram hostProgram)
|
||||
public ProgramToSave(CachedShaderProgram cachedProgram, IProgram hostProgram, byte[] binaryCode)
|
||||
{
|
||||
CachedProgram = cachedProgram;
|
||||
HostProgram = hostProgram;
|
||||
BinaryCode = binaryCode;
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,9 +100,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
|
||||
_programsToSaveQueue = new Queue<ProgramToSave>();
|
||||
|
||||
string diskCacheTitleId = GraphicsConfig.EnableShaderCache && GraphicsConfig.TitleId != null
|
||||
? CacheHelper.GetBaseCacheDirectory(GraphicsConfig.TitleId)
|
||||
: null;
|
||||
string diskCacheTitleId = GetDiskCachePath();
|
||||
|
||||
_computeShaderCache = new ComputeShaderCacheHashTable();
|
||||
_graphicsShaderCache = new ShaderCacheHashTable();
|
||||
@ -108,6 +112,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path where the disk cache for the current application is stored.
|
||||
/// </summary>
|
||||
private static string GetDiskCachePath()
|
||||
{
|
||||
return GraphicsConfig.EnableShaderCache && GraphicsConfig.TitleId != null
|
||||
? Path.Combine(AppDataManager.GamesDirPath, GraphicsConfig.TitleId, "cache", "shader")
|
||||
: null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the queue of shaders that must save their binaries to the disk cache.
|
||||
/// </summary>
|
||||
@ -123,7 +137,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
if (result == ProgramLinkStatus.Success)
|
||||
{
|
||||
_cacheWriter.AddShader(programToSave.CachedProgram, programToSave.HostProgram.GetBinary());
|
||||
_cacheWriter.AddShader(programToSave.CachedProgram, programToSave.BinaryCode ?? programToSave.HostProgram.GetBinary());
|
||||
}
|
||||
|
||||
_programsToSaveQueue.Dequeue();
|
||||
@ -143,16 +157,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
if (_diskCacheHostStorage.CacheEnabled)
|
||||
{
|
||||
if (!_diskCacheHostStorage.CacheExists())
|
||||
{
|
||||
// If we don't have a shader cache on the new format, try to perform migration from the old shader cache.
|
||||
Logger.Info?.Print(LogClass.Gpu, "No shader cache found, trying to migrate from legacy shader cache...");
|
||||
|
||||
int migrationCount = Migration.MigrateFromLegacyCache(_context, _diskCacheHostStorage);
|
||||
|
||||
Logger.Info?.Print(LogClass.Gpu, $"Migrated {migrationCount} shaders.");
|
||||
}
|
||||
|
||||
ParallelDiskCacheLoader loader = new ParallelDiskCacheLoader(
|
||||
_context,
|
||||
_graphicsShaderCache,
|
||||
@ -210,25 +214,74 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
return cpShader;
|
||||
}
|
||||
|
||||
ShaderSpecializationState specState = new ShaderSpecializationState(computeState);
|
||||
ShaderSpecializationState specState = new ShaderSpecializationState(ref computeState);
|
||||
GpuAccessorState gpuAccessorState = new GpuAccessorState(poolState, computeState, default, specState);
|
||||
GpuAccessor gpuAccessor = new GpuAccessor(_context, channel, gpuAccessorState);
|
||||
|
||||
TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, gpuVa);
|
||||
TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, gpuVa);
|
||||
|
||||
TranslatedShader translatedShader = TranslateShader(_dumper, channel, translatorContext, cachedGuestCode);
|
||||
|
||||
IProgram hostProgram = _context.Renderer.CreateProgram(new ShaderSource[] { CreateShaderSource(translatedShader.Program) }, new ShaderInfo(-1));
|
||||
ShaderSource[] shaderSourcesArray = new ShaderSource[] { CreateShaderSource(translatedShader.Program) };
|
||||
|
||||
IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, new ShaderInfo(-1));
|
||||
|
||||
cpShader = new CachedShaderProgram(hostProgram, specState, translatedShader.Shader);
|
||||
|
||||
_computeShaderCache.Add(cpShader);
|
||||
EnqueueProgramToSave(new ProgramToSave(cpShader, hostProgram));
|
||||
EnqueueProgramToSave(cpShader, hostProgram, shaderSourcesArray);
|
||||
_cpPrograms[gpuVa] = cpShader;
|
||||
|
||||
return cpShader;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the shader pipeline state based on the current GPU state.
|
||||
/// </summary>
|
||||
/// <param name="state">Current GPU 3D engine state</param>
|
||||
/// <param name="pipeline">Shader pipeline state to be updated</param>
|
||||
/// <param name="graphicsState">Current graphics state</param>
|
||||
/// <param name="channel">Current GPU channel</param>
|
||||
private void UpdatePipelineInfo(
|
||||
ref ThreedClassState state,
|
||||
ref ProgramPipelineState pipeline,
|
||||
GpuChannelGraphicsState graphicsState,
|
||||
GpuChannel channel)
|
||||
{
|
||||
channel.TextureManager.UpdateRenderTargets();
|
||||
|
||||
var rtControl = state.RtControl;
|
||||
var msaaMode = state.RtMsaaMode;
|
||||
|
||||
pipeline.SamplesCount = msaaMode.SamplesInX() * msaaMode.SamplesInY();
|
||||
|
||||
int count = rtControl.UnpackCount();
|
||||
|
||||
for (int index = 0; index < Constants.TotalRenderTargets; index++)
|
||||
{
|
||||
int rtIndex = rtControl.UnpackPermutationIndex(index);
|
||||
|
||||
var colorState = state.RtColorState[rtIndex];
|
||||
|
||||
if (index >= count || colorState.Format == 0 || colorState.WidthOrStride == 0)
|
||||
{
|
||||
pipeline.AttachmentEnable[index] = false;
|
||||
pipeline.AttachmentFormats[index] = Format.R8G8B8A8Unorm;
|
||||
}
|
||||
else
|
||||
{
|
||||
pipeline.AttachmentEnable[index] = true;
|
||||
pipeline.AttachmentFormats[index] = colorState.Format.Convert().Format;
|
||||
}
|
||||
}
|
||||
|
||||
pipeline.DepthStencilEnable = state.RtDepthStencilEnable;
|
||||
pipeline.DepthStencilFormat = pipeline.DepthStencilEnable ? state.RtDepthStencilState.Format.Convert().Format : Format.D24UnormS8Uint;
|
||||
|
||||
pipeline.VertexBufferCount = Constants.TotalVertexBuffers;
|
||||
pipeline.Topology = graphicsState.Topology;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a graphics shader program from the shader cache.
|
||||
/// This includes all the specified shader stages.
|
||||
@ -237,6 +290,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// This automatically translates, compiles and adds the code to the cache if not present.
|
||||
/// </remarks>
|
||||
/// <param name="state">GPU state</param>
|
||||
/// <param name="pipeline">Pipeline state</param>
|
||||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="poolState">Texture pool state</param>
|
||||
/// <param name="graphicsState">3D engine state</param>
|
||||
@ -244,6 +298,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// <returns>Compiled graphics shader code</returns>
|
||||
public CachedShaderProgram GetGraphicsShader(
|
||||
ref ThreedClassState state,
|
||||
ref ProgramPipelineState pipeline,
|
||||
GpuChannel channel,
|
||||
GpuChannelPoolState poolState,
|
||||
GpuChannelGraphicsState graphicsState,
|
||||
@ -262,7 +317,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
|
||||
TransformFeedbackDescriptor[] transformFeedbackDescriptors = GetTransformFeedbackDescriptors(ref state);
|
||||
|
||||
ShaderSpecializationState specState = new ShaderSpecializationState(graphicsState, transformFeedbackDescriptors);
|
||||
UpdatePipelineInfo(ref state, ref pipeline, graphicsState, channel);
|
||||
|
||||
ShaderSpecializationState specState = new ShaderSpecializationState(ref graphicsState, ref pipeline, transformFeedbackDescriptors);
|
||||
GpuAccessorState gpuAccessorState = new GpuAccessorState(poolState, default, graphicsState, specState, transformFeedbackDescriptors);
|
||||
|
||||
ReadOnlySpan<ulong> addressesSpan = addresses.AsSpan();
|
||||
@ -270,6 +327,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1];
|
||||
TranslatorContext nextStage = null;
|
||||
|
||||
TargetApi api = _context.Capabilities.Api;
|
||||
|
||||
for (int stageIndex = Constants.ShaderStages - 1; stageIndex >= 0; stageIndex--)
|
||||
{
|
||||
ulong gpuVa = addressesSpan[stageIndex + 1];
|
||||
@ -277,7 +336,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
if (gpuVa != 0)
|
||||
{
|
||||
GpuAccessor gpuAccessor = new GpuAccessor(_context, channel, gpuAccessorState, stageIndex);
|
||||
TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, DefaultFlags, gpuVa);
|
||||
TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags, gpuVa);
|
||||
|
||||
if (nextStage != null)
|
||||
{
|
||||
@ -286,7 +345,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
|
||||
if (stageIndex == 0 && addresses.VertexA != 0)
|
||||
{
|
||||
translatorContexts[0] = DecodeGraphicsShader(gpuAccessor, DefaultFlags | TranslationFlags.VertexA, addresses.VertexA);
|
||||
translatorContexts[0] = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags | TranslationFlags.VertexA, addresses.VertexA);
|
||||
}
|
||||
|
||||
translatorContexts[stageIndex + 1] = currentStage;
|
||||
@ -336,13 +395,15 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
}
|
||||
}
|
||||
|
||||
ShaderSource[] shaderSourcesArray = shaderSources.ToArray();
|
||||
|
||||
int fragmentOutputMap = shaders[5]?.Info.FragmentOutputMap ?? -1;
|
||||
IProgram hostProgram = _context.Renderer.CreateProgram(shaderSources.ToArray(), new ShaderInfo(fragmentOutputMap));
|
||||
IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, new ShaderInfo(fragmentOutputMap, pipeline));
|
||||
|
||||
gpShaders = new CachedShaderProgram(hostProgram, specState, shaders);
|
||||
|
||||
_graphicsShaderCache.Add(gpShaders);
|
||||
EnqueueProgramToSave(new ProgramToSave(gpShaders, hostProgram));
|
||||
EnqueueProgramToSave(gpShaders, hostProgram, shaderSourcesArray);
|
||||
_gpPrograms[addresses] = gpShaders;
|
||||
|
||||
return gpShaders;
|
||||
@ -355,7 +416,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// <returns>Shader source</returns>
|
||||
public static ShaderSource CreateShaderSource(ShaderProgram program)
|
||||
{
|
||||
return new ShaderSource(program.Code, program.BinaryCode, program.Info.Stage, program.Language);
|
||||
return new ShaderSource(program.Code, program.BinaryCode, GetBindings(program.Info), program.Info.Stage, program.Language);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -364,11 +425,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// <remarks>
|
||||
/// This will not do anything if disk shader cache is disabled.
|
||||
/// </remarks>
|
||||
/// <param name="programToSave">Program to be saved on disk</param>
|
||||
private void EnqueueProgramToSave(ProgramToSave programToSave)
|
||||
/// <param name="program">Cached shader program</param>
|
||||
/// <param name="hostProgram">Host program</param>
|
||||
/// <param name="sources">Source for each shader stage</param>
|
||||
private void EnqueueProgramToSave(CachedShaderProgram program, IProgram hostProgram, ShaderSource[] sources)
|
||||
{
|
||||
if (_diskCacheHostStorage.CacheEnabled)
|
||||
{
|
||||
byte[] binaryCode = _context.Capabilities.Api == TargetApi.Vulkan ? ShaderBinarySerializer.Pack(sources) : null;
|
||||
ProgramToSave programToSave = new ProgramToSave(program, hostProgram, binaryCode);
|
||||
|
||||
_programsToSaveQueue.Enqueue(programToSave);
|
||||
}
|
||||
}
|
||||
@ -480,11 +546,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// Decode the binary Maxwell shader code to a translator context.
|
||||
/// </summary>
|
||||
/// <param name="gpuAccessor">GPU state accessor</param>
|
||||
/// <param name="api">Graphics API that will be used with the shader</param>
|
||||
/// <param name="gpuVa">GPU virtual address of the binary shader code</param>
|
||||
/// <returns>The generated translator context</returns>
|
||||
public static TranslatorContext DecodeComputeShader(IGpuAccessor gpuAccessor, ulong gpuVa)
|
||||
public static TranslatorContext DecodeComputeShader(IGpuAccessor gpuAccessor, TargetApi api, ulong gpuVa)
|
||||
{
|
||||
var options = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, DefaultFlags | TranslationFlags.Compute);
|
||||
var options = CreateTranslationOptions(api, DefaultFlags | TranslationFlags.Compute);
|
||||
return Translator.CreateContext(gpuVa, gpuAccessor, options);
|
||||
}
|
||||
|
||||
@ -495,12 +562,13 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// This will combine the "Vertex A" and "Vertex B" shader stages, if specified, into one shader.
|
||||
/// </remarks>
|
||||
/// <param name="gpuAccessor">GPU state accessor</param>
|
||||
/// <param name="api">Graphics API that will be used with the shader</param>
|
||||
/// <param name="flags">Flags that controls shader translation</param>
|
||||
/// <param name="gpuVa">GPU virtual address of the shader code</param>
|
||||
/// <returns>The generated translator context</returns>
|
||||
public static TranslatorContext DecodeGraphicsShader(IGpuAccessor gpuAccessor, TranslationFlags flags, ulong gpuVa)
|
||||
public static TranslatorContext DecodeGraphicsShader(IGpuAccessor gpuAccessor, TargetApi api, TranslationFlags flags, ulong gpuVa)
|
||||
{
|
||||
var options = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, flags);
|
||||
var options = CreateTranslationOptions(api, flags);
|
||||
return Translator.CreateContext(gpuVa, gpuAccessor, options);
|
||||
}
|
||||
|
||||
@ -595,6 +663,41 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets information about the bindings used by a shader program.
|
||||
/// </summary>
|
||||
/// <param name="info">Shader program information to get the information from</param>
|
||||
/// <returns>Shader bindings</returns>
|
||||
public static ShaderBindings GetBindings(ShaderProgramInfo info)
|
||||
{
|
||||
var uniformBufferBindings = info.CBuffers.Select(x => x.Binding).ToArray();
|
||||
var storageBufferBindings = info.SBuffers.Select(x => x.Binding).ToArray();
|
||||
var textureBindings = info.Textures.Select(x => x.Binding).ToArray();
|
||||
var imageBindings = info.Images.Select(x => x.Binding).ToArray();
|
||||
|
||||
return new ShaderBindings(
|
||||
uniformBufferBindings,
|
||||
storageBufferBindings,
|
||||
textureBindings,
|
||||
imageBindings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates shader translation options with the requested graphics API and flags.
|
||||
/// The shader language is choosen based on the current configuration and graphics API.
|
||||
/// </summary>
|
||||
/// <param name="api">Target graphics API</param>
|
||||
/// <param name="flags">Translation flags</param>
|
||||
/// <returns>Translation options</returns>
|
||||
private static TranslationOptions CreateTranslationOptions(TargetApi api, TranslationFlags flags)
|
||||
{
|
||||
TargetLanguage lang = GraphicsConfig.EnableSpirvCompilationOnVulkan && api == TargetApi.Vulkan
|
||||
? TargetLanguage.Spirv
|
||||
: TargetLanguage.Glsl;
|
||||
|
||||
return new TranslationOptions(lang, api, flags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the shader cache, deleting all the cached shaders.
|
||||
/// It's an error to use the shader cache after disposal.
|
||||
|
@ -1,6 +1,7 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Shader.DiskCache;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System;
|
||||
@ -19,6 +20,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
private const uint TfbdMagic = (byte)'T' | ((byte)'F' << 8) | ((byte)'B' << 16) | ((byte)'D' << 24);
|
||||
private const uint TexkMagic = (byte)'T' | ((byte)'E' << 8) | ((byte)'X' << 16) | ((byte)'K' << 24);
|
||||
private const uint TexsMagic = (byte)'T' | ((byte)'E' << 8) | ((byte)'X' << 16) | ((byte)'S' << 24);
|
||||
private const uint PgpsMagic = (byte)'P' | ((byte)'G' << 8) | ((byte)'P' << 16) | ((byte)'S' << 24);
|
||||
|
||||
/// <summary>
|
||||
/// Flags indicating GPU state that is used by the shader.
|
||||
@ -51,6 +53,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// </summary>
|
||||
public Array5<uint> ConstantBufferUse;
|
||||
|
||||
/// <summary>
|
||||
/// Pipeline state captured at the time of shader use.
|
||||
/// </summary>
|
||||
public ProgramPipelineState? PipelineState;
|
||||
|
||||
/// <summary>
|
||||
/// Transform feedback buffers active at the time the shader was compiled.
|
||||
/// </summary>
|
||||
@ -179,7 +186,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// Creates a new instance of the shader specialization state.
|
||||
/// </summary>
|
||||
/// <param name="state">Current compute engine state</param>
|
||||
public ShaderSpecializationState(GpuChannelComputeState state) : this()
|
||||
public ShaderSpecializationState(ref GpuChannelComputeState state) : this()
|
||||
{
|
||||
ComputeState = state;
|
||||
_compute = true;
|
||||
@ -190,7 +197,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// </summary>
|
||||
/// <param name="state">Current 3D engine state</param>
|
||||
/// <param name="descriptors">Optional transform feedback buffers in use, if any</param>
|
||||
public ShaderSpecializationState(GpuChannelGraphicsState state, TransformFeedbackDescriptor[] descriptors) : this()
|
||||
private ShaderSpecializationState(ref GpuChannelGraphicsState state, TransformFeedbackDescriptor[] descriptors) : this()
|
||||
{
|
||||
GraphicsState = state;
|
||||
_compute = false;
|
||||
@ -244,6 +251,34 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the shader specialization state.
|
||||
/// </summary>
|
||||
/// <param name="state">Current 3D engine state</param>
|
||||
/// <param name="pipelineState">Current program pipeline state</param>
|
||||
/// <param name="descriptors">Optional transform feedback buffers in use, if any</param>
|
||||
public ShaderSpecializationState(
|
||||
ref GpuChannelGraphicsState state,
|
||||
ref ProgramPipelineState pipelineState,
|
||||
TransformFeedbackDescriptor[] descriptors) : this(ref state, descriptors)
|
||||
{
|
||||
PipelineState = pipelineState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the shader specialization state.
|
||||
/// </summary>
|
||||
/// <param name="state">Current 3D engine state</param>
|
||||
/// <param name="pipelineState">Current program pipeline state</param>
|
||||
/// <param name="descriptors">Optional transform feedback buffers in use, if any</param>
|
||||
public ShaderSpecializationState(
|
||||
ref GpuChannelGraphicsState state,
|
||||
ProgramPipelineState? pipelineState,
|
||||
TransformFeedbackDescriptor[] descriptors) : this(ref state, descriptors)
|
||||
{
|
||||
PipelineState = pipelineState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the shader accesses the early Z force state.
|
||||
/// </summary>
|
||||
@ -463,6 +498,28 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
return false;
|
||||
}
|
||||
|
||||
if (graphicsState.DepthMode != GraphicsState.DepthMode)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (graphicsState.AlphaTestEnable != GraphicsState.AlphaTestEnable)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (graphicsState.AlphaTestEnable &&
|
||||
(graphicsState.AlphaTestCompare != GraphicsState.AlphaTestCompare ||
|
||||
graphicsState.AlphaTestReference != GraphicsState.AlphaTestReference))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!graphicsState.AttributeTypes.ToSpan().SequenceEqual(GraphicsState.AttributeTypes.ToSpan()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Matches(channel, poolState, checkTextures, isCompute: false);
|
||||
}
|
||||
|
||||
@ -685,6 +742,17 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
constantBufferUsePerStageMask &= ~(1 << index);
|
||||
}
|
||||
|
||||
bool hasPipelineState = false;
|
||||
|
||||
dataReader.Read(ref hasPipelineState);
|
||||
|
||||
if (hasPipelineState)
|
||||
{
|
||||
ProgramPipelineState pipelineState = default;
|
||||
dataReader.ReadWithMagicAndSize(ref pipelineState, PgpsMagic);
|
||||
specState.PipelineState = pipelineState;
|
||||
}
|
||||
|
||||
if (specState._queriedState.HasFlag(QueriedStateFlags.TransformFeedback))
|
||||
{
|
||||
ushort tfCount = 0;
|
||||
@ -743,6 +811,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
constantBufferUsePerStageMask &= ~(1 << index);
|
||||
}
|
||||
|
||||
bool hasPipelineState = PipelineState.HasValue;
|
||||
|
||||
dataWriter.Write(ref hasPipelineState);
|
||||
|
||||
if (hasPipelineState)
|
||||
{
|
||||
ProgramPipelineState pipelineState = PipelineState.Value;
|
||||
dataWriter.WriteWithMagicAndSize(ref pipelineState, PgpsMagic);
|
||||
}
|
||||
|
||||
if (_queriedState.HasFlag(QueriedStateFlags.TransformFeedback))
|
||||
{
|
||||
ushort tfCount = (ushort)TransformFeedbackDescriptors.Length;
|
||||
|
Reference in New Issue
Block a user