mirror of
https://github.com/Ryujinx/Ryujinx.git
synced 2024-08-18 22:58:43 +02:00
* 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>
1627 lines
63 KiB
C#
1627 lines
63 KiB
C#
using Ryujinx.Common;
|
|
using Ryujinx.Common.Logging;
|
|
using Ryujinx.Graphics.GAL;
|
|
using Ryujinx.Graphics.Gpu.Memory;
|
|
using Ryujinx.Graphics.Texture;
|
|
using Ryujinx.Graphics.Texture.Astc;
|
|
using Ryujinx.Memory;
|
|
using Ryujinx.Memory.Range;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Numerics;
|
|
|
|
namespace Ryujinx.Graphics.Gpu.Image
|
|
{
|
|
/// <summary>
|
|
/// Represents a cached GPU texture.
|
|
/// </summary>
|
|
class Texture : IMultiRangeItem, IDisposable
|
|
{
|
|
// How many updates we need before switching to the byte-by-byte comparison
|
|
// modification check method.
|
|
// This method uses much more memory so we want to avoid it if possible.
|
|
private const int ByteComparisonSwitchThreshold = 4;
|
|
|
|
private const int MinLevelsForForceAnisotropy = 5;
|
|
|
|
private struct TexturePoolOwner
|
|
{
|
|
public TexturePool Pool;
|
|
public int ID;
|
|
}
|
|
|
|
private GpuContext _context;
|
|
private PhysicalMemory _physicalMemory;
|
|
|
|
private SizeInfo _sizeInfo;
|
|
|
|
/// <summary>
|
|
/// Texture format.
|
|
/// </summary>
|
|
public Format Format => Info.FormatInfo.Format;
|
|
|
|
/// <summary>
|
|
/// Texture target.
|
|
/// </summary>
|
|
public Target Target { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Texture width.
|
|
/// </summary>
|
|
public int Width { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Texture height.
|
|
/// </summary>
|
|
public int Height { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Texture information.
|
|
/// </summary>
|
|
public TextureInfo Info { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Set when anisotropic filtering can be forced on the given texture.
|
|
/// </summary>
|
|
public bool CanForceAnisotropy { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Host scale factor.
|
|
/// </summary>
|
|
public float ScaleFactor { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Upscaling mode. Informs if a texture is scaled, or is eligible for scaling.
|
|
/// </summary>
|
|
public TextureScaleMode ScaleMode { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Group that this texture belongs to. Manages read/write memory tracking.
|
|
/// </summary>
|
|
public TextureGroup Group { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Set when a texture has been changed size. This indicates that it may need to be
|
|
/// changed again when obtained as a sampler.
|
|
/// </summary>
|
|
public bool ChangedSize { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Set when a texture's GPU VA has ever been partially or fully unmapped.
|
|
/// This indicates that the range must be fully checked when matching the texture.
|
|
/// </summary>
|
|
public bool ChangedMapping { get; private set; }
|
|
|
|
/// <summary>
|
|
/// True if the data for this texture must always be flushed when an overlap appears.
|
|
/// This is useful if SetData is called directly on this texture, but the data is meant for a future texture.
|
|
/// </summary>
|
|
public bool AlwaysFlushOnOverlap { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Increments when the host texture is swapped, or when the texture is removed from all pools.
|
|
/// </summary>
|
|
public int InvalidatedSequence { get; private set; }
|
|
|
|
private int _depth;
|
|
private int _layers;
|
|
public int FirstLayer { get; private set; }
|
|
public int FirstLevel { get; private set; }
|
|
|
|
private bool _hasData;
|
|
private bool _dirty = true;
|
|
private int _updateCount;
|
|
private byte[] _currentData;
|
|
|
|
private bool _modifiedStale = true;
|
|
|
|
private ITexture _arrayViewTexture;
|
|
private Target _arrayViewTarget;
|
|
|
|
private ITexture _flushHostTexture;
|
|
|
|
private Texture _viewStorage;
|
|
|
|
private List<Texture> _views;
|
|
|
|
/// <summary>
|
|
/// Host texture.
|
|
/// </summary>
|
|
public ITexture HostTexture { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Intrusive linked list node used on the auto deletion texture cache.
|
|
/// </summary>
|
|
public LinkedListNode<Texture> CacheNode { get; set; }
|
|
|
|
/// <summary>
|
|
/// Event to fire when texture data is disposed.
|
|
/// </summary>
|
|
public event Action<Texture> Disposed;
|
|
|
|
/// <summary>
|
|
/// Physical memory ranges where the texture data is located.
|
|
/// </summary>
|
|
public MultiRange Range { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Layer size in bytes.
|
|
/// </summary>
|
|
public int LayerSize => _sizeInfo.LayerSize;
|
|
|
|
/// <summary>
|
|
/// Texture size in bytes.
|
|
/// </summary>
|
|
public ulong Size => (ulong)_sizeInfo.TotalSize;
|
|
|
|
/// <summary>
|
|
/// Whether or not the texture belongs is a view.
|
|
/// </summary>
|
|
public bool IsView => _viewStorage != this;
|
|
|
|
private int _referenceCount;
|
|
private List<TexturePoolOwner> _poolOwners;
|
|
|
|
/// <summary>
|
|
/// Constructs a new instance of the cached GPU texture.
|
|
/// </summary>
|
|
/// <param name="context">GPU context that the texture belongs to</param>
|
|
/// <param name="physicalMemory">Physical memory where the texture is mapped</param>
|
|
/// <param name="info">Texture information</param>
|
|
/// <param name="sizeInfo">Size information of the texture</param>
|
|
/// <param name="range">Physical memory ranges where the texture data is located</param>
|
|
/// <param name="firstLayer">The first layer of the texture, or 0 if the texture has no parent</param>
|
|
/// <param name="firstLevel">The first mipmap level of the texture, or 0 if the texture has no parent</param>
|
|
/// <param name="scaleFactor">The floating point scale factor to initialize with</param>
|
|
/// <param name="scaleMode">The scale mode to initialize with</param>
|
|
private Texture(
|
|
GpuContext context,
|
|
PhysicalMemory physicalMemory,
|
|
TextureInfo info,
|
|
SizeInfo sizeInfo,
|
|
MultiRange range,
|
|
int firstLayer,
|
|
int firstLevel,
|
|
float scaleFactor,
|
|
TextureScaleMode scaleMode)
|
|
{
|
|
InitializeTexture(context, physicalMemory, info, sizeInfo, range);
|
|
|
|
FirstLayer = firstLayer;
|
|
FirstLevel = firstLevel;
|
|
|
|
ScaleFactor = scaleFactor;
|
|
ScaleMode = scaleMode;
|
|
|
|
InitializeData(true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a new instance of the cached GPU texture.
|
|
/// </summary>
|
|
/// <param name="context">GPU context that the texture belongs to</param>
|
|
/// <param name="physicalMemory">Physical memory where the texture is mapped</param>
|
|
/// <param name="info">Texture information</param>
|
|
/// <param name="sizeInfo">Size information of the texture</param>
|
|
/// <param name="range">Physical memory ranges where the texture data is located</param>
|
|
/// <param name="scaleMode">The scale mode to initialize with. If scaled, the texture's data is loaded immediately and scaled up</param>
|
|
public Texture(
|
|
GpuContext context,
|
|
PhysicalMemory physicalMemory,
|
|
TextureInfo info,
|
|
SizeInfo sizeInfo,
|
|
MultiRange range,
|
|
TextureScaleMode scaleMode)
|
|
{
|
|
ScaleFactor = 1f; // Texture is first loaded at scale 1x.
|
|
ScaleMode = scaleMode;
|
|
|
|
InitializeTexture(context, physicalMemory, info, sizeInfo, range);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Common texture initialization method.
|
|
/// This sets the context, info and sizeInfo fields.
|
|
/// Other fields are initialized with their default values.
|
|
/// </summary>
|
|
/// <param name="context">GPU context that the texture belongs to</param>
|
|
/// <param name="physicalMemory">Physical memory where the texture is mapped</param>
|
|
/// <param name="info">Texture information</param>
|
|
/// <param name="sizeInfo">Size information of the texture</param>
|
|
/// <param name="range">Physical memory ranges where the texture data is located</param>
|
|
private void InitializeTexture(
|
|
GpuContext context,
|
|
PhysicalMemory physicalMemory,
|
|
TextureInfo info,
|
|
SizeInfo sizeInfo,
|
|
MultiRange range)
|
|
{
|
|
_context = context;
|
|
_physicalMemory = physicalMemory;
|
|
_sizeInfo = sizeInfo;
|
|
Range = range;
|
|
|
|
SetInfo(info);
|
|
|
|
_viewStorage = this;
|
|
|
|
_views = new List<Texture>();
|
|
_poolOwners = new List<TexturePoolOwner>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes the data for a texture. Can optionally initialize the texture with or without data.
|
|
/// If the texture is a view, it will initialize memory tracking to be non-dirty.
|
|
/// </summary>
|
|
/// <param name="isView">True if the texture is a view, false otherwise</param>
|
|
/// <param name="withData">True if the texture is to be initialized with data</param>
|
|
public void InitializeData(bool isView, bool withData = false)
|
|
{
|
|
withData |= Group != null && Group.FlushIncompatibleOverlapsIfNeeded();
|
|
|
|
if (withData)
|
|
{
|
|
Debug.Assert(!isView);
|
|
|
|
TextureCreateInfo createInfo = TextureCache.GetCreateInfo(Info, _context.Capabilities, ScaleFactor);
|
|
HostTexture = _context.Renderer.CreateTexture(createInfo, ScaleFactor);
|
|
|
|
SynchronizeMemory(); // Load the data.
|
|
if (ScaleMode == TextureScaleMode.Scaled)
|
|
{
|
|
SetScale(GraphicsConfig.ResScale); // Scale the data up.
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_hasData = true;
|
|
|
|
if (!isView)
|
|
{
|
|
// Don't update this texture the next time we synchronize.
|
|
CheckModified(true);
|
|
|
|
if (ScaleMode == TextureScaleMode.Scaled)
|
|
{
|
|
// Don't need to start at 1x as there is no data to scale, just go straight to the target scale.
|
|
ScaleFactor = GraphicsConfig.ResScale;
|
|
}
|
|
|
|
TextureCreateInfo createInfo = TextureCache.GetCreateInfo(Info, _context.Capabilities, ScaleFactor);
|
|
HostTexture = _context.Renderer.CreateTexture(createInfo, ScaleFactor);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initialize a new texture group with this texture as storage.
|
|
/// </summary>
|
|
/// <param name="hasLayerViews">True if the texture will have layer views</param>
|
|
/// <param name="hasMipViews">True if the texture will have mip views</param>
|
|
/// <param name="incompatibleOverlaps">Groups that overlap with this one but are incompatible</param>
|
|
public void InitializeGroup(bool hasLayerViews, bool hasMipViews, List<TextureIncompatibleOverlap> incompatibleOverlaps)
|
|
{
|
|
Group = new TextureGroup(_context, _physicalMemory, this, incompatibleOverlaps);
|
|
|
|
Group.Initialize(ref _sizeInfo, hasLayerViews, hasMipViews);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a texture view from this texture.
|
|
/// A texture view is defined as a child texture, from a sub-range of their parent texture.
|
|
/// For example, the initial layer and mipmap level of the view can be defined, so the texture
|
|
/// will start at the given layer/level of the parent texture.
|
|
/// </summary>
|
|
/// <param name="info">Child texture information</param>
|
|
/// <param name="sizeInfo">Child texture size information</param>
|
|
/// <param name="range">Physical memory ranges where the texture data is located</param>
|
|
/// <param name="firstLayer">Start layer of the child texture on the parent texture</param>
|
|
/// <param name="firstLevel">Start mipmap level of the child texture on the parent texture</param>
|
|
/// <returns>The child texture</returns>
|
|
public Texture CreateView(TextureInfo info, SizeInfo sizeInfo, MultiRange range, int firstLayer, int firstLevel)
|
|
{
|
|
Texture texture = new Texture(
|
|
_context,
|
|
_physicalMemory,
|
|
info,
|
|
sizeInfo,
|
|
range,
|
|
FirstLayer + firstLayer,
|
|
FirstLevel + firstLevel,
|
|
ScaleFactor,
|
|
ScaleMode);
|
|
|
|
TextureCreateInfo createInfo = TextureCache.GetCreateInfo(info, _context.Capabilities, ScaleFactor);
|
|
texture.HostTexture = HostTexture.CreateView(createInfo, firstLayer, firstLevel);
|
|
|
|
_viewStorage.AddView(texture);
|
|
|
|
return texture;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a child texture to this texture.
|
|
/// </summary>
|
|
/// <param name="texture">The child texture</param>
|
|
private void AddView(Texture texture)
|
|
{
|
|
IncrementReferenceCount();
|
|
|
|
_views.Add(texture);
|
|
|
|
texture._viewStorage = this;
|
|
|
|
Group.UpdateViews(_views);
|
|
|
|
if (texture.Group != null && texture.Group != Group)
|
|
{
|
|
if (texture.Group.Storage == texture)
|
|
{
|
|
// This texture's group is no longer used.
|
|
Group.Inherit(texture.Group);
|
|
|
|
texture.Group.Dispose();
|
|
}
|
|
}
|
|
|
|
texture.Group = Group;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a child texture from this texture.
|
|
/// </summary>
|
|
/// <param name="texture">The child texture</param>
|
|
private void RemoveView(Texture texture)
|
|
{
|
|
_views.Remove(texture);
|
|
|
|
texture._viewStorage = texture;
|
|
|
|
DecrementReferenceCount();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a copy dependency to a texture that is view compatible with this one.
|
|
/// When either texture is modified, the texture data will be copied to the other to keep them in sync.
|
|
/// This is essentially an emulated view, useful for handling multiple view parents or format incompatibility.
|
|
/// This also forces a copy on creation, to or from the given texture to get them in sync immediately.
|
|
/// </summary>
|
|
/// <param name="contained">The view compatible texture to create a dependency to</param>
|
|
/// <param name="layer">The base layer of the given texture relative to this one</param>
|
|
/// <param name="level">The base level of the given texture relative to this one</param>
|
|
/// <param name="copyTo">True if this texture is first copied to the given one, false for the opposite direction</param>
|
|
public void CreateCopyDependency(Texture contained, int layer, int level, bool copyTo)
|
|
{
|
|
if (contained.Group == Group)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Group.CreateCopyDependency(contained, FirstLayer + layer, FirstLevel + level, copyTo);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Changes the texture size.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This operation may also change the size of all mipmap levels, including from the parent
|
|
/// and other possible child textures, to ensure that all sizes are consistent.
|
|
/// </remarks>
|
|
/// <param name="width">The new texture width</param>
|
|
/// <param name="height">The new texture height</param>
|
|
/// <param name="depthOrLayers">The new texture depth (for 3D textures) or layers (for layered textures)</param>
|
|
public void ChangeSize(int width, int height, int depthOrLayers)
|
|
{
|
|
int blockWidth = Info.FormatInfo.BlockWidth;
|
|
int blockHeight = Info.FormatInfo.BlockHeight;
|
|
|
|
width <<= FirstLevel;
|
|
height <<= FirstLevel;
|
|
|
|
if (Target == Target.Texture3D)
|
|
{
|
|
depthOrLayers <<= FirstLevel;
|
|
}
|
|
else
|
|
{
|
|
depthOrLayers = _viewStorage.Info.DepthOrLayers;
|
|
}
|
|
|
|
_viewStorage.RecreateStorageOrView(width, height, blockWidth, blockHeight, depthOrLayers);
|
|
|
|
foreach (Texture view in _viewStorage._views)
|
|
{
|
|
int viewWidth = Math.Max(1, width >> view.FirstLevel);
|
|
int viewHeight = Math.Max(1, height >> view.FirstLevel);
|
|
|
|
int viewDepthOrLayers;
|
|
|
|
if (view.Info.Target == Target.Texture3D)
|
|
{
|
|
viewDepthOrLayers = Math.Max(1, depthOrLayers >> view.FirstLevel);
|
|
}
|
|
else
|
|
{
|
|
viewDepthOrLayers = view.Info.DepthOrLayers;
|
|
}
|
|
|
|
view.RecreateStorageOrView(viewWidth, viewHeight, blockWidth, blockHeight, viewDepthOrLayers);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recreates the texture storage (or view, in the case of child textures) of this texture.
|
|
/// This allows recreating the texture with a new size.
|
|
/// A copy is automatically performed from the old to the new texture.
|
|
/// </summary>
|
|
/// <param name="width">The new texture width</param>
|
|
/// <param name="height">The new texture height</param>
|
|
/// <param name="width">The block width related to the given width</param>
|
|
/// <param name="height">The block height related to the given height</param>
|
|
/// <param name="depthOrLayers">The new texture depth (for 3D textures) or layers (for layered textures)</param>
|
|
private void RecreateStorageOrView(int width, int height, int blockWidth, int blockHeight, int depthOrLayers)
|
|
{
|
|
RecreateStorageOrView(
|
|
BitUtils.DivRoundUp(width * Info.FormatInfo.BlockWidth, blockWidth),
|
|
BitUtils.DivRoundUp(height * Info.FormatInfo.BlockHeight, blockHeight),
|
|
depthOrLayers);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recreates the texture storage (or view, in the case of child textures) of this texture.
|
|
/// This allows recreating the texture with a new size.
|
|
/// A copy is automatically performed from the old to the new texture.
|
|
/// </summary>
|
|
/// <param name="width">The new texture width</param>
|
|
/// <param name="height">The new texture height</param>
|
|
/// <param name="depthOrLayers">The new texture depth (for 3D textures) or layers (for layered textures)</param>
|
|
private void RecreateStorageOrView(int width, int height, int depthOrLayers)
|
|
{
|
|
ChangedSize = true;
|
|
|
|
SetInfo(new TextureInfo(
|
|
Info.GpuAddress,
|
|
width,
|
|
height,
|
|
depthOrLayers,
|
|
Info.Levels,
|
|
Info.SamplesInX,
|
|
Info.SamplesInY,
|
|
Info.Stride,
|
|
Info.IsLinear,
|
|
Info.GobBlocksInY,
|
|
Info.GobBlocksInZ,
|
|
Info.GobBlocksInTileX,
|
|
Info.Target,
|
|
Info.FormatInfo,
|
|
Info.DepthStencilMode,
|
|
Info.SwizzleR,
|
|
Info.SwizzleG,
|
|
Info.SwizzleB,
|
|
Info.SwizzleA));
|
|
|
|
TextureCreateInfo createInfo = TextureCache.GetCreateInfo(Info, _context.Capabilities, ScaleFactor);
|
|
|
|
if (_viewStorage != this)
|
|
{
|
|
ReplaceStorage(_viewStorage.HostTexture.CreateView(createInfo, FirstLayer, FirstLevel));
|
|
}
|
|
else
|
|
{
|
|
ITexture newStorage = _context.Renderer.CreateTexture(createInfo, ScaleFactor);
|
|
|
|
HostTexture.CopyTo(newStorage, 0, 0);
|
|
|
|
ReplaceStorage(newStorage);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Blacklists this texture from being scaled. Resets its scale to 1 if needed.
|
|
/// </summary>
|
|
public void BlacklistScale()
|
|
{
|
|
ScaleMode = TextureScaleMode.Blacklisted;
|
|
SetScale(1f);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Propagates the scale between this texture and another to ensure they have the same scale.
|
|
/// If one texture is blacklisted from scaling, the other will become blacklisted too.
|
|
/// </summary>
|
|
/// <param name="other">The other texture</param>
|
|
public void PropagateScale(Texture other)
|
|
{
|
|
if (other.ScaleMode == TextureScaleMode.Blacklisted || ScaleMode == TextureScaleMode.Blacklisted)
|
|
{
|
|
BlacklistScale();
|
|
other.BlacklistScale();
|
|
}
|
|
else
|
|
{
|
|
// Prefer the configured scale if present. If not, prefer the max.
|
|
float targetScale = GraphicsConfig.ResScale;
|
|
float sharedScale = (ScaleFactor == targetScale || other.ScaleFactor == targetScale) ? targetScale : Math.Max(ScaleFactor, other.ScaleFactor);
|
|
|
|
SetScale(sharedScale);
|
|
other.SetScale(sharedScale);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copy the host texture to a scaled one. If a texture is not provided, create it with the given scale.
|
|
/// </summary>
|
|
/// <param name="scale">Scale factor</param>
|
|
/// <param name="storage">Texture to use instead of creating one</param>
|
|
/// <returns>A host texture containing a scaled version of this texture</returns>
|
|
private ITexture GetScaledHostTexture(float scale, ITexture storage = null)
|
|
{
|
|
if (storage == null)
|
|
{
|
|
TextureCreateInfo createInfo = TextureCache.GetCreateInfo(Info, _context.Capabilities, scale);
|
|
storage = _context.Renderer.CreateTexture(createInfo, scale);
|
|
}
|
|
|
|
HostTexture.CopyTo(storage, new Extents2D(0, 0, HostTexture.Width, HostTexture.Height), new Extents2D(0, 0, storage.Width, storage.Height), true);
|
|
|
|
return storage;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the Scale Factor on this texture, and immediately recreates it at the correct size.
|
|
/// When a texture is resized, a scaled copy is performed from the old texture to the new one, to ensure no data is lost.
|
|
/// If scale is equivalent, this only propagates the blacklisted/scaled mode.
|
|
/// If called on a view, its storage is resized instead.
|
|
/// When resizing storage, all texture views are recreated.
|
|
/// </summary>
|
|
/// <param name="scale">The new scale factor for this texture</param>
|
|
public void SetScale(float scale)
|
|
{
|
|
bool unscaled = ScaleMode == TextureScaleMode.Blacklisted || (ScaleMode == TextureScaleMode.Undesired && scale == 1);
|
|
TextureScaleMode newScaleMode = unscaled ? ScaleMode : TextureScaleMode.Scaled;
|
|
|
|
if (_viewStorage != this)
|
|
{
|
|
_viewStorage.ScaleMode = newScaleMode;
|
|
_viewStorage.SetScale(scale);
|
|
return;
|
|
}
|
|
|
|
if (ScaleFactor != scale)
|
|
{
|
|
Logger.Debug?.Print(LogClass.Gpu, $"Rescaling {Info.Width}x{Info.Height} {Info.FormatInfo.Format.ToString()} to ({ScaleFactor} to {scale}). ");
|
|
|
|
ScaleFactor = scale;
|
|
|
|
ITexture newStorage = GetScaledHostTexture(ScaleFactor);
|
|
|
|
Logger.Debug?.Print(LogClass.Gpu, $" Copy performed: {HostTexture.Width}x{HostTexture.Height} to {newStorage.Width}x{newStorage.Height}");
|
|
|
|
ReplaceStorage(newStorage);
|
|
|
|
// All views must be recreated against the new storage.
|
|
|
|
foreach (var view in _views)
|
|
{
|
|
Logger.Debug?.Print(LogClass.Gpu, $" Recreating view {Info.Width}x{Info.Height} {Info.FormatInfo.Format.ToString()}.");
|
|
view.ScaleFactor = scale;
|
|
|
|
TextureCreateInfo viewCreateInfo = TextureCache.GetCreateInfo(view.Info, _context.Capabilities, scale);
|
|
ITexture newView = HostTexture.CreateView(viewCreateInfo, view.FirstLayer - FirstLayer, view.FirstLevel - FirstLevel);
|
|
|
|
view.ReplaceStorage(newView);
|
|
view.ScaleMode = newScaleMode;
|
|
}
|
|
}
|
|
|
|
if (ScaleMode != newScaleMode)
|
|
{
|
|
ScaleMode = newScaleMode;
|
|
|
|
foreach (var view in _views)
|
|
{
|
|
view.ScaleMode = newScaleMode;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if the memory for this texture was modified, and returns true if it was.
|
|
/// The modified flags are optionally consumed as a result.
|
|
/// </summary>
|
|
/// <param name="consume">True to consume the dirty flags and reprotect, false to leave them as is</param>
|
|
/// <returns>True if the texture was modified, false otherwise.</returns>
|
|
public bool CheckModified(bool consume)
|
|
{
|
|
return Group.CheckDirty(this, consume);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Synchronizes guest and host memory.
|
|
/// This will overwrite the texture data with the texture data on the guest memory, if a CPU
|
|
/// modification is detected.
|
|
/// Be aware that this can cause texture data written by the GPU to be lost, this is just a
|
|
/// one way copy (from CPU owned to GPU owned memory).
|
|
/// </summary>
|
|
public void SynchronizeMemory()
|
|
{
|
|
if (Target == Target.TextureBuffer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!_dirty)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_dirty = false;
|
|
|
|
if (_hasData)
|
|
{
|
|
Group.SynchronizeMemory(this);
|
|
}
|
|
else
|
|
{
|
|
Group.CheckDirty(this, true);
|
|
SynchronizeFull();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Signal that this texture is dirty, indicating that the texture group must be checked.
|
|
/// </summary>
|
|
public void SignalGroupDirty()
|
|
{
|
|
_dirty = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Signal that the modified state is dirty, indicating that the texture group should be notified when it changes.
|
|
/// </summary>
|
|
public void SignalModifiedDirty()
|
|
{
|
|
_modifiedStale = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fully synchronizes guest and host memory.
|
|
/// This will replace the entire texture with the data present in guest memory.
|
|
/// </summary>
|
|
public void SynchronizeFull()
|
|
{
|
|
if (_hasData)
|
|
{
|
|
BlacklistScale();
|
|
}
|
|
|
|
ReadOnlySpan<byte> data = _physicalMemory.GetSpan(Range);
|
|
|
|
// If the host does not support ASTC compression, we need to do the decompression.
|
|
// The decompression is slow, so we want to avoid it as much as possible.
|
|
// This does a byte-by-byte check and skips the update if the data is equal in this case.
|
|
// This improves the speed on applications that overwrites ASTC data without changing anything.
|
|
if (Info.FormatInfo.Format.IsAstc() && !_context.Capabilities.SupportsAstcCompression)
|
|
{
|
|
if (_updateCount < ByteComparisonSwitchThreshold)
|
|
{
|
|
_updateCount++;
|
|
}
|
|
else
|
|
{
|
|
bool dataMatches = _currentData != null && data.SequenceEqual(_currentData);
|
|
_currentData = data.ToArray();
|
|
if (dataMatches)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
data = ConvertToHostCompatibleFormat(data);
|
|
|
|
HostTexture.SetData(data);
|
|
|
|
_hasData = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Uploads new texture data to the host GPU.
|
|
/// </summary>
|
|
/// <param name="data">New data</param>
|
|
public void SetData(ReadOnlySpan<byte> data)
|
|
{
|
|
BlacklistScale();
|
|
|
|
Group.CheckDirty(this, true);
|
|
|
|
AlwaysFlushOnOverlap = true;
|
|
|
|
HostTexture.SetData(data);
|
|
|
|
_hasData = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Uploads new texture data to the host GPU for a specific layer/level.
|
|
/// </summary>
|
|
/// <param name="data">New data</param>
|
|
/// <param name="layer">Target layer</param>
|
|
/// <param name="level">Target level</param>
|
|
public void SetData(ReadOnlySpan<byte> data, int layer, int level)
|
|
{
|
|
BlacklistScale();
|
|
|
|
HostTexture.SetData(data, layer, level);
|
|
|
|
_currentData = null;
|
|
|
|
_hasData = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts texture data to a format and layout that is supported by the host GPU.
|
|
/// </summary>
|
|
/// <param name="data">Data to be converted</param>
|
|
/// <param name="level">Mip level to convert</param>
|
|
/// <param name="single">True to convert a single slice</param>
|
|
/// <returns>Converted data</returns>
|
|
public ReadOnlySpan<byte> ConvertToHostCompatibleFormat(ReadOnlySpan<byte> data, int level = 0, bool single = false)
|
|
{
|
|
int width = Info.Width;
|
|
int height = Info.Height;
|
|
|
|
int depth = _depth;
|
|
int layers = single ? 1 : _layers;
|
|
int levels = single ? 1 : (Info.Levels - level);
|
|
|
|
width = Math.Max(width >> level, 1);
|
|
height = Math.Max(height >> level, 1);
|
|
depth = Math.Max(depth >> level, 1);
|
|
|
|
if (Info.IsLinear)
|
|
{
|
|
data = LayoutConverter.ConvertLinearStridedToLinear(
|
|
width,
|
|
height,
|
|
Info.FormatInfo.BlockWidth,
|
|
Info.FormatInfo.BlockHeight,
|
|
Info.Stride,
|
|
Info.Stride,
|
|
Info.FormatInfo.BytesPerPixel,
|
|
data);
|
|
}
|
|
else
|
|
{
|
|
data = LayoutConverter.ConvertBlockLinearToLinear(
|
|
width,
|
|
height,
|
|
depth,
|
|
single ? 1 : depth,
|
|
levels,
|
|
layers,
|
|
Info.FormatInfo.BlockWidth,
|
|
Info.FormatInfo.BlockHeight,
|
|
Info.FormatInfo.BytesPerPixel,
|
|
Info.GobBlocksInY,
|
|
Info.GobBlocksInZ,
|
|
Info.GobBlocksInTileX,
|
|
_sizeInfo,
|
|
data);
|
|
}
|
|
|
|
// Handle compressed cases not supported by the host:
|
|
// - ASTC is usually not supported on desktop cards.
|
|
// - BC4/BC5 is not supported on 3D textures.
|
|
if (!_context.Capabilities.SupportsAstcCompression && Format.IsAstc())
|
|
{
|
|
if (!AstcDecoder.TryDecodeToRgba8P(
|
|
data.ToArray(),
|
|
Info.FormatInfo.BlockWidth,
|
|
Info.FormatInfo.BlockHeight,
|
|
width,
|
|
height,
|
|
depth,
|
|
levels,
|
|
layers,
|
|
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 (!TextureCompatibility.HostSupportsBcFormat(Format, Target, _context.Capabilities))
|
|
{
|
|
switch (Format)
|
|
{
|
|
case Format.Bc1RgbaSrgb:
|
|
case Format.Bc1RgbaUnorm:
|
|
data = BCnDecoder.DecodeBC1(data, width, height, depth, levels, layers);
|
|
break;
|
|
case Format.Bc2Srgb:
|
|
case Format.Bc2Unorm:
|
|
data = BCnDecoder.DecodeBC2(data, width, height, depth, levels, layers);
|
|
break;
|
|
case Format.Bc3Srgb:
|
|
case Format.Bc3Unorm:
|
|
data = BCnDecoder.DecodeBC3(data, width, height, depth, levels, layers);
|
|
break;
|
|
case Format.Bc4Snorm:
|
|
case Format.Bc4Unorm:
|
|
data = BCnDecoder.DecodeBC4(data, width, height, depth, levels, layers, Format == Format.Bc4Snorm);
|
|
break;
|
|
case Format.Bc5Snorm:
|
|
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;
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts texture data from a format and layout that is supported by the host GPU, back into the intended format on the guest GPU.
|
|
/// </summary>
|
|
/// <param name="output">Optional output span to convert into</param>
|
|
/// <param name="data">Data to be converted</param>
|
|
/// <param name="level">Mip level to convert</param>
|
|
/// <param name="single">True to convert a single slice</param>
|
|
/// <returns>Converted data</returns>
|
|
public ReadOnlySpan<byte> ConvertFromHostCompatibleFormat(Span<byte> output, ReadOnlySpan<byte> data, int level = 0, bool single = false)
|
|
{
|
|
if (Target != Target.TextureBuffer)
|
|
{
|
|
int width = Info.Width;
|
|
int height = Info.Height;
|
|
|
|
int depth = _depth;
|
|
int layers = single ? 1 : _layers;
|
|
int levels = single ? 1 : (Info.Levels - level);
|
|
|
|
width = Math.Max(width >> level, 1);
|
|
height = Math.Max(height >> level, 1);
|
|
depth = Math.Max(depth >> level, 1);
|
|
|
|
if (Info.IsLinear)
|
|
{
|
|
data = LayoutConverter.ConvertLinearToLinearStrided(
|
|
output,
|
|
Info.Width,
|
|
Info.Height,
|
|
Info.FormatInfo.BlockWidth,
|
|
Info.FormatInfo.BlockHeight,
|
|
Info.Stride,
|
|
Info.FormatInfo.BytesPerPixel,
|
|
data);
|
|
}
|
|
else
|
|
{
|
|
data = LayoutConverter.ConvertLinearToBlockLinear(
|
|
output,
|
|
width,
|
|
height,
|
|
depth,
|
|
single ? 1 : depth,
|
|
levels,
|
|
layers,
|
|
Info.FormatInfo.BlockWidth,
|
|
Info.FormatInfo.BlockHeight,
|
|
Info.FormatInfo.BytesPerPixel,
|
|
Info.GobBlocksInY,
|
|
Info.GobBlocksInZ,
|
|
Info.GobBlocksInTileX,
|
|
_sizeInfo,
|
|
data);
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Flushes the texture data.
|
|
/// This causes the texture data to be written back to guest memory.
|
|
/// If the texture was written by the GPU, this includes all modification made by the GPU
|
|
/// up to this point.
|
|
/// Be aware that this is an expensive operation, avoid calling it unless strictly needed.
|
|
/// This may cause data corruption if the memory is already being used for something else on the CPU side.
|
|
/// </summary>
|
|
/// <param name="tracked">Whether or not the flush triggers write tracking. If it doesn't, the texture will not be blacklisted for scaling either.</param>
|
|
/// <returns>True if data was flushed, false otherwise</returns>
|
|
public bool FlushModified(bool tracked = true)
|
|
{
|
|
return TextureCompatibility.CanTextureFlush(Info, _context.Capabilities) && Group.FlushModified(this, tracked);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Flushes the texture data.
|
|
/// This causes the texture data to be written back to guest memory.
|
|
/// If the texture was written by the GPU, this includes all modification made by the GPU
|
|
/// up to this point.
|
|
/// Be aware that this is an expensive operation, avoid calling it unless strictly needed.
|
|
/// This may cause data corruption if the memory is already being used for something else on the CPU side.
|
|
/// </summary>
|
|
/// <param name="tracked">Whether or not the flush triggers write tracking. If it doesn't, the texture will not be blacklisted for scaling either.</param>
|
|
public void Flush(bool tracked)
|
|
{
|
|
if (TextureCompatibility.CanTextureFlush(Info, _context.Capabilities))
|
|
{
|
|
FlushTextureDataToGuest(tracked);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a host texture to use for flushing the texture, at 1x resolution.
|
|
/// If the HostTexture is already at 1x resolution, it is returned directly.
|
|
/// </summary>
|
|
/// <returns>The host texture to flush</returns>
|
|
public ITexture GetFlushTexture()
|
|
{
|
|
ITexture texture = HostTexture;
|
|
if (ScaleFactor != 1f)
|
|
{
|
|
// If needed, create a texture to flush back to host at 1x scale.
|
|
texture = _flushHostTexture = GetScaledHostTexture(1f, _flushHostTexture);
|
|
}
|
|
|
|
return texture;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets data from the host GPU, and flushes it all to guest memory.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This method should be used to retrieve data that was modified by the host GPU.
|
|
/// This is not cheap, avoid doing that unless strictly needed.
|
|
/// When possible, the data is written directly into guest memory, rather than copied.
|
|
/// </remarks>
|
|
/// <param name="tracked">True if writing the texture data is tracked, false otherwise</param>
|
|
/// <param name="texture">The specific host texture to flush. Defaults to this texture</param>
|
|
public void FlushTextureDataToGuest(bool tracked, ITexture texture = null)
|
|
{
|
|
using WritableRegion region = _physicalMemory.GetWritableRegion(Range, tracked);
|
|
|
|
GetTextureDataFromGpu(region.Memory.Span, tracked, texture);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets data from the host GPU.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This method should be used to retrieve data that was modified by the host GPU.
|
|
/// This is not cheap, avoid doing that unless strictly needed.
|
|
/// </remarks>
|
|
/// <param name="output">An output span to place the texture data into. If empty, one is generated</param>
|
|
/// <param name="blacklist">True if the texture should be blacklisted, false otherwise</param>
|
|
/// <param name="texture">The specific host texture to flush. Defaults to this texture</param>
|
|
/// <returns>The span containing the texture data</returns>
|
|
private ReadOnlySpan<byte> GetTextureDataFromGpu(Span<byte> output, bool blacklist, ITexture texture = null)
|
|
{
|
|
ReadOnlySpan<byte> data;
|
|
|
|
if (texture != null)
|
|
{
|
|
data = texture.GetData();
|
|
}
|
|
else
|
|
{
|
|
if (blacklist)
|
|
{
|
|
BlacklistScale();
|
|
data = HostTexture.GetData();
|
|
}
|
|
else if (ScaleFactor != 1f)
|
|
{
|
|
float scale = ScaleFactor;
|
|
SetScale(1f);
|
|
data = HostTexture.GetData();
|
|
SetScale(scale);
|
|
}
|
|
else
|
|
{
|
|
data = HostTexture.GetData();
|
|
}
|
|
}
|
|
|
|
data = ConvertFromHostCompatibleFormat(output, data);
|
|
|
|
return data;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets data from the host GPU for a single slice.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This method should be used to retrieve data that was modified by the host GPU.
|
|
/// This is not cheap, avoid doing that unless strictly needed.
|
|
/// </remarks>
|
|
/// <param name="output">An output span to place the texture data into. If empty, one is generated</param>
|
|
/// <param name="layer">The layer of the texture to flush</param>
|
|
/// <param name="level">The level of the texture to flush</param>
|
|
/// <param name="blacklist">True if the texture should be blacklisted, false otherwise</param>
|
|
/// <param name="texture">The specific host texture to flush. Defaults to this texture</param>
|
|
/// <returns>The span containing the texture data</returns>
|
|
public ReadOnlySpan<byte> GetTextureDataSliceFromGpu(Span<byte> output, int layer, int level, bool blacklist, ITexture texture = null)
|
|
{
|
|
ReadOnlySpan<byte> data;
|
|
|
|
if (texture != null)
|
|
{
|
|
data = texture.GetData(layer, level);
|
|
}
|
|
else
|
|
{
|
|
if (blacklist)
|
|
{
|
|
BlacklistScale();
|
|
data = HostTexture.GetData(layer, level);
|
|
}
|
|
else if (ScaleFactor != 1f)
|
|
{
|
|
float scale = ScaleFactor;
|
|
SetScale(1f);
|
|
data = HostTexture.GetData(layer, level);
|
|
SetScale(scale);
|
|
}
|
|
else
|
|
{
|
|
data = HostTexture.GetData(layer, level);
|
|
}
|
|
}
|
|
|
|
data = ConvertFromHostCompatibleFormat(output, data, level, true);
|
|
|
|
return data;
|
|
}
|
|
|
|
/// <summary>
|
|
/// This performs a strict comparison, used to check if this texture is equal to the one supplied.
|
|
/// </summary>
|
|
/// <param name="info">Texture information to compare against</param>
|
|
/// <param name="flags">Comparison flags</param>
|
|
/// <returns>A value indicating how well this texture matches the given info</returns>
|
|
public TextureMatchQuality IsExactMatch(TextureInfo info, TextureSearchFlags flags)
|
|
{
|
|
TextureMatchQuality matchQuality = TextureCompatibility.FormatMatches(Info, info, (flags & TextureSearchFlags.ForSampler) != 0, (flags & TextureSearchFlags.ForCopy) != 0);
|
|
|
|
if (matchQuality == TextureMatchQuality.NoMatch)
|
|
{
|
|
return matchQuality;
|
|
}
|
|
|
|
if (!TextureCompatibility.LayoutMatches(Info, info))
|
|
{
|
|
return TextureMatchQuality.NoMatch;
|
|
}
|
|
|
|
if (!TextureCompatibility.SizeMatches(Info, info, (flags & TextureSearchFlags.Strict) == 0, FirstLevel))
|
|
{
|
|
return TextureMatchQuality.NoMatch;
|
|
}
|
|
|
|
if ((flags & TextureSearchFlags.ForSampler) != 0 || (flags & TextureSearchFlags.Strict) != 0)
|
|
{
|
|
if (!TextureCompatibility.SamplerParamsMatches(Info, info))
|
|
{
|
|
return TextureMatchQuality.NoMatch;
|
|
}
|
|
}
|
|
|
|
if ((flags & TextureSearchFlags.ForCopy) != 0)
|
|
{
|
|
bool msTargetCompatible = Info.Target == Target.Texture2DMultisample && info.Target == Target.Texture2D;
|
|
|
|
if (!msTargetCompatible && !TextureCompatibility.TargetAndSamplesCompatible(Info, info))
|
|
{
|
|
return TextureMatchQuality.NoMatch;
|
|
}
|
|
}
|
|
else if (!TextureCompatibility.TargetAndSamplesCompatible(Info, info))
|
|
{
|
|
return TextureMatchQuality.NoMatch;
|
|
}
|
|
|
|
return Info.Levels == info.Levels ? matchQuality : TextureMatchQuality.NoMatch;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if it's possible to create a view, with the given parameters, from this texture.
|
|
/// </summary>
|
|
/// <param name="info">Texture view information</param>
|
|
/// <param name="range">Texture view physical memory ranges</param>
|
|
/// <param name="layerSize">Layer size on the given texture</param>
|
|
/// <param name="caps">Host GPU capabilities</param>
|
|
/// <param name="firstLayer">Texture view initial layer on this texture</param>
|
|
/// <param name="firstLevel">Texture view first mipmap level on this texture</param>
|
|
/// <returns>The level of compatiblilty a view with the given parameters created from this texture has</returns>
|
|
public TextureViewCompatibility IsViewCompatible(TextureInfo info, MultiRange range, int layerSize, Capabilities caps, out int firstLayer, out int firstLevel)
|
|
{
|
|
TextureViewCompatibility result = TextureViewCompatibility.Full;
|
|
|
|
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewFormatCompatible(Info, info, caps));
|
|
if (result != TextureViewCompatibility.Incompatible)
|
|
{
|
|
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))
|
|
{
|
|
result = TextureViewCompatibility.Incompatible;
|
|
}
|
|
|
|
if (result == TextureViewCompatibility.Full && Info.FormatInfo.Format != info.FormatInfo.Format && !_context.Capabilities.SupportsMismatchingViewFormat)
|
|
{
|
|
// AMD and Intel have a bug where the view format is always ignored;
|
|
// they use the parent format instead.
|
|
// Create a copy dependency to avoid this issue.
|
|
|
|
result = TextureViewCompatibility.CopyOnly;
|
|
}
|
|
}
|
|
|
|
firstLayer = 0;
|
|
firstLevel = 0;
|
|
|
|
if (result == TextureViewCompatibility.Incompatible)
|
|
{
|
|
return TextureViewCompatibility.Incompatible;
|
|
}
|
|
|
|
int offset = Range.FindOffset(range);
|
|
|
|
if (offset < 0 || !_sizeInfo.FindView(offset, out firstLayer, out firstLevel))
|
|
{
|
|
return TextureViewCompatibility.LayoutIncompatible;
|
|
}
|
|
|
|
if (!TextureCompatibility.ViewLayoutCompatible(Info, info, firstLevel))
|
|
{
|
|
return TextureViewCompatibility.LayoutIncompatible;
|
|
}
|
|
|
|
if (info.GetSlices() > 1 && LayerSize != layerSize)
|
|
{
|
|
return TextureViewCompatibility.LayoutIncompatible;
|
|
}
|
|
|
|
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewSizeMatches(Info, info, firstLevel));
|
|
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewSubImagesInBounds(Info, info, firstLayer, firstLevel));
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a texture of the specified target type from this texture.
|
|
/// This can be used to get an array texture from a non-array texture and vice-versa.
|
|
/// If this texture and the requested targets are equal, then this texture Host texture is returned directly.
|
|
/// </summary>
|
|
/// <param name="target">The desired target type</param>
|
|
/// <returns>A view of this texture with the requested target, or null if the target is invalid for this texture</returns>
|
|
public ITexture GetTargetTexture(Target target)
|
|
{
|
|
if (target == Target)
|
|
{
|
|
return HostTexture;
|
|
}
|
|
|
|
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,
|
|
formatInfo.BlockWidth,
|
|
formatInfo.BlockHeight,
|
|
formatInfo.BytesPerPixel,
|
|
formatInfo.Format,
|
|
Info.DepthStencilMode,
|
|
target,
|
|
Info.SwizzleR,
|
|
Info.SwizzleG,
|
|
Info.SwizzleB,
|
|
Info.SwizzleA);
|
|
|
|
ITexture viewTexture = HostTexture.CreateView(createInfo, 0, 0);
|
|
|
|
_arrayViewTexture = viewTexture;
|
|
_arrayViewTarget = target;
|
|
|
|
return viewTexture;
|
|
}
|
|
else if (_arrayViewTarget == target)
|
|
{
|
|
return _arrayViewTexture;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determine if this texture can have anisotropic filtering forced.
|
|
/// Filtered textures that we might want to force anisotropy on should have a lot of mip levels.
|
|
/// </summary>
|
|
/// <returns>True if anisotropic filtering can be forced, false otherwise</returns>
|
|
private bool CanTextureForceAnisotropy()
|
|
{
|
|
if (!(Target == Target.Texture2D || Target == Target.Texture2DArray))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int maxSize = Math.Max(Info.Width, Info.Height);
|
|
int maxLevels = BitOperations.Log2((uint)maxSize) + 1;
|
|
|
|
return Info.Levels >= Math.Min(MinLevelsForForceAnisotropy, maxLevels);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if this texture and the specified target have the same number of dimensions.
|
|
/// For the purposes of this comparison, 2D and 2D Multisample textures are not considered to have
|
|
/// the same number of dimensions. Same for Cubemap and 3D textures.
|
|
/// </summary>
|
|
/// <param name="target">The target to compare with</param>
|
|
/// <returns>True if both targets have the same number of dimensions, false otherwise</returns>
|
|
private bool IsSameDimensionsTarget(Target target)
|
|
{
|
|
switch (Info.Target)
|
|
{
|
|
case Target.Texture1D:
|
|
case Target.Texture1DArray:
|
|
return target == Target.Texture1D ||
|
|
target == Target.Texture1DArray;
|
|
|
|
case Target.Texture2D:
|
|
case Target.Texture2DArray:
|
|
return target == Target.Texture2D ||
|
|
target == Target.Texture2DArray;
|
|
|
|
case Target.Cubemap:
|
|
case Target.CubemapArray:
|
|
return target == Target.Cubemap ||
|
|
target == Target.CubemapArray;
|
|
|
|
case Target.Texture2DMultisample:
|
|
case Target.Texture2DMultisampleArray:
|
|
return target == Target.Texture2DMultisample ||
|
|
target == Target.Texture2DMultisampleArray;
|
|
|
|
case Target.Texture3D:
|
|
return target == Target.Texture3D;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Replaces view texture information.
|
|
/// This should only be used for child textures with a parent.
|
|
/// </summary>
|
|
/// <param name="parent">The parent texture</param>
|
|
/// <param name="info">The new view texture information</param>
|
|
/// <param name="hostTexture">The new host texture</param>
|
|
/// <param name="firstLayer">The first layer of the view</param>
|
|
/// <param name="firstLevel">The first level of the view</param>
|
|
public void ReplaceView(Texture parent, TextureInfo info, ITexture hostTexture, int firstLayer, int firstLevel)
|
|
{
|
|
IncrementReferenceCount();
|
|
parent._viewStorage.SynchronizeMemory();
|
|
|
|
// If this texture has views, they must be given to the new parent.
|
|
if (_views.Count > 0)
|
|
{
|
|
Texture[] viewCopy = _views.ToArray();
|
|
|
|
foreach (Texture view in viewCopy)
|
|
{
|
|
TextureCreateInfo createInfo = TextureCache.GetCreateInfo(view.Info, _context.Capabilities, ScaleFactor);
|
|
|
|
ITexture newView = parent.HostTexture.CreateView(createInfo, view.FirstLayer + firstLayer, view.FirstLevel + firstLevel);
|
|
|
|
view.ReplaceView(parent, view.Info, newView, view.FirstLayer + firstLayer, view.FirstLevel + firstLevel);
|
|
}
|
|
}
|
|
|
|
ReplaceStorage(hostTexture);
|
|
|
|
if (_viewStorage != this)
|
|
{
|
|
_viewStorage.RemoveView(this);
|
|
}
|
|
|
|
FirstLayer = parent.FirstLayer + firstLayer;
|
|
FirstLevel = parent.FirstLevel + firstLevel;
|
|
parent._viewStorage.AddView(this);
|
|
|
|
SetInfo(info);
|
|
DecrementReferenceCount();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the internal texture information structure.
|
|
/// </summary>
|
|
/// <param name="info">The new texture information</param>
|
|
private void SetInfo(TextureInfo info)
|
|
{
|
|
Info = info;
|
|
Target = info.Target;
|
|
Width = info.Width;
|
|
Height = info.Height;
|
|
CanForceAnisotropy = CanTextureForceAnisotropy();
|
|
|
|
_depth = info.GetDepth();
|
|
_layers = info.GetLayers();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Signals that the texture has been modified.
|
|
/// </summary>
|
|
public void SignalModified()
|
|
{
|
|
if (_modifiedStale || Group.HasCopyDependencies)
|
|
{
|
|
_modifiedStale = false;
|
|
Group.SignalModified(this);
|
|
}
|
|
|
|
_physicalMemory.TextureCache.Lift(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Signals that a texture has been bound, or has been unbound.
|
|
/// During this time, lazy copies will not clear the dirty flag.
|
|
/// </summary>
|
|
/// <param name="bound">True if the texture has been bound, false if it has been unbound</param>
|
|
public void SignalModifying(bool bound)
|
|
{
|
|
if (_modifiedStale || Group.HasCopyDependencies)
|
|
{
|
|
_modifiedStale = false;
|
|
Group.SignalModifying(this, bound);
|
|
}
|
|
|
|
_physicalMemory.TextureCache.Lift(this);
|
|
|
|
if (bound)
|
|
{
|
|
IncrementReferenceCount();
|
|
}
|
|
else
|
|
{
|
|
DecrementReferenceCount();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Replaces the host texture, while disposing of the old one if needed.
|
|
/// </summary>
|
|
/// <param name="hostTexture">The new host texture</param>
|
|
private void ReplaceStorage(ITexture hostTexture)
|
|
{
|
|
DisposeTextures();
|
|
|
|
HostTexture = hostTexture;
|
|
InvalidatedSequence++;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determine if any of this texture's data overlaps with another.
|
|
/// </summary>
|
|
/// <param name="texture">The texture to check against</param>
|
|
/// <param name="compatibility">The view compatibility of the two textures</param>
|
|
/// <returns>True if any slice of the textures overlap, false otherwise</returns>
|
|
public bool DataOverlaps(Texture texture, TextureViewCompatibility compatibility)
|
|
{
|
|
if (compatibility == TextureViewCompatibility.LayoutIncompatible && Info.GobBlocksInZ > 1 && Info.GobBlocksInZ == texture.Info.GobBlocksInZ)
|
|
{
|
|
// Allow overlapping slices of layout compatible 3D textures with matching GobBlocksInZ, as they are interleaved.
|
|
return false;
|
|
}
|
|
|
|
if (texture._sizeInfo.AllOffsets.Length == 1 && _sizeInfo.AllOffsets.Length == 1)
|
|
{
|
|
return Range.OverlapsWith(texture.Range);
|
|
}
|
|
|
|
MultiRange otherRange = texture.Range;
|
|
|
|
IEnumerable<MultiRange> regions = _sizeInfo.AllRegions().Select((region) => Range.GetSlice((ulong)region.Offset, (ulong)region.Size));
|
|
IEnumerable<MultiRange> otherRegions = texture._sizeInfo.AllRegions().Select((region) => otherRange.GetSlice((ulong)region.Offset, (ulong)region.Size));
|
|
|
|
foreach (MultiRange region in regions)
|
|
{
|
|
foreach (MultiRange otherRegion in otherRegions)
|
|
{
|
|
if (region.OverlapsWith(otherRegion))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Increments the texture reference count.
|
|
/// </summary>
|
|
public void IncrementReferenceCount()
|
|
{
|
|
_referenceCount++;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Increments the reference count and records the given texture pool and ID as a pool owner.
|
|
/// </summary>
|
|
/// <param name="pool">The texture pool this texture has been added to</param>
|
|
/// <param name="id">The ID of the reference to this texture in the pool</param>
|
|
public void IncrementReferenceCount(TexturePool pool, int id)
|
|
{
|
|
lock (_poolOwners)
|
|
{
|
|
_poolOwners.Add(new TexturePoolOwner { Pool = pool, ID = id });
|
|
}
|
|
_referenceCount++;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Decrements the texture reference count.
|
|
/// When the reference count hits zero, the texture may be deleted and can't be used anymore.
|
|
/// </summary>
|
|
/// <returns>True if the texture is now referenceless, false otherwise</returns>
|
|
public bool DecrementReferenceCount()
|
|
{
|
|
int newRefCount = --_referenceCount;
|
|
|
|
if (newRefCount == 0)
|
|
{
|
|
if (_viewStorage != this)
|
|
{
|
|
_viewStorage.RemoveView(this);
|
|
}
|
|
|
|
_physicalMemory.TextureCache.RemoveTextureFromCache(this);
|
|
}
|
|
|
|
Debug.Assert(newRefCount >= 0);
|
|
|
|
DeleteIfNotUsed();
|
|
|
|
return newRefCount <= 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Decrements the texture reference count, also removing an associated pool owner reference.
|
|
/// When the reference count hits zero, the texture may be deleted and can't be used anymore.
|
|
/// </summary>
|
|
/// <param name="pool">The texture pool this texture is being removed from</param>
|
|
/// <param name="id">The ID of the reference to this texture in the pool</param>
|
|
/// <returns>True if the texture is now referenceless, false otherwise</returns>
|
|
public bool DecrementReferenceCount(TexturePool pool, int id = -1)
|
|
{
|
|
lock (_poolOwners)
|
|
{
|
|
int references = _poolOwners.RemoveAll(entry => entry.Pool == pool && entry.ID == id || id == -1);
|
|
|
|
if (references == 0)
|
|
{
|
|
// This reference has already been removed.
|
|
return _referenceCount <= 0;
|
|
}
|
|
|
|
Debug.Assert(references == 1);
|
|
}
|
|
|
|
return DecrementReferenceCount();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Forcibly remove this texture from all pools that reference it.
|
|
/// </summary>
|
|
/// <param name="deferred">Indicates if the removal is being done from another thread.</param>
|
|
public void RemoveFromPools(bool deferred)
|
|
{
|
|
lock (_poolOwners)
|
|
{
|
|
foreach (var owner in _poolOwners)
|
|
{
|
|
owner.Pool.ForceRemove(this, owner.ID, deferred);
|
|
}
|
|
|
|
_poolOwners.Clear();
|
|
}
|
|
|
|
InvalidatedSequence++;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Delete the texture if it is not used anymore.
|
|
/// The texture is considered unused when the reference count is zero,
|
|
/// and it has no child views.
|
|
/// </summary>
|
|
private void DeleteIfNotUsed()
|
|
{
|
|
// We can delete the texture as long it is not being used
|
|
// in any cache (the reference count is 0 in this case), and
|
|
// also all views that may be created from this texture were
|
|
// already deleted (views count is 0).
|
|
if (_referenceCount == 0 && _views.Count == 0)
|
|
{
|
|
Dispose();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Performs texture disposal, deleting the texture.
|
|
/// </summary>
|
|
private void DisposeTextures()
|
|
{
|
|
_currentData = null;
|
|
HostTexture.Release();
|
|
|
|
_arrayViewTexture?.Release();
|
|
_arrayViewTexture = null;
|
|
|
|
_flushHostTexture?.Release();
|
|
_flushHostTexture = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when the memory for this texture has been unmapped.
|
|
/// Calls are from non-gpu threads.
|
|
/// </summary>
|
|
/// <param name="unmapRange">The range of memory being unmapped</param>
|
|
public void Unmapped(MultiRange unmapRange)
|
|
{
|
|
ChangedMapping = true;
|
|
|
|
if (Group.Storage == this)
|
|
{
|
|
Group.ClearModified(unmapRange);
|
|
}
|
|
|
|
RemoveFromPools(true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Performs texture disposal, deleting the texture.
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
DisposeTextures();
|
|
|
|
Disposed?.Invoke(this);
|
|
|
|
if (Group.Storage == this)
|
|
{
|
|
Group.Dispose();
|
|
}
|
|
}
|
|
}
|
|
} |