Added ufbx warper
This commit is contained in:
91
src/ThridParty/Ghost.Nvtt/Wrapper/NvttBatchList.cs
Normal file
91
src/ThridParty/Ghost.Nvtt/Wrapper/NvttBatchList.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
namespace Ghost.Nvtt.Wrapper;
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper around an nvtt batch list — a list of (surface, face, mipmap,
|
||||
/// outputOptions) tuples passed to <see cref="NvttContext.CompressBatch"/>.
|
||||
/// </summary>
|
||||
public sealed unsafe class NvttBatchListHandle : IDisposable
|
||||
{
|
||||
private NvttBatchList* _ptr;
|
||||
|
||||
/// <summary>Raw pointer - use only when calling the native API directly.</summary>
|
||||
public NvttBatchList* Ptr => _ptr;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Construction / destruction
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public NvttBatchListHandle() => _ptr = Api.nvttCreateBatchList();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_ptr != null)
|
||||
{
|
||||
Api.nvttDestroyBatchList(_ptr);
|
||||
_ptr = null;
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Mutation
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Removes all items from the list.</summary>
|
||||
public void Clear()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Api.nvttBatchListClear(_ptr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends an entry. The <paramref name="surface"/> and
|
||||
/// <paramref name="outputOptions"/> must remain alive for the duration of
|
||||
/// any subsequent <see cref="NvttContext.CompressBatch"/> call.
|
||||
/// </summary>
|
||||
public void Append(NvttSurfaceHandle surface, int face, int mipmap,
|
||||
NvttOutputOptionsHandle outputOptions)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Api.nvttBatchListAppend(_ptr, surface.Ptr, face, mipmap,
|
||||
outputOptions.Ptr);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Query
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Number of items currently in the list.</summary>
|
||||
public uint Count
|
||||
{
|
||||
get { ThrowIfDisposed(); return Api.nvttBatchListGetSize(_ptr); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the raw pointers for item <paramref name="index"/>.
|
||||
/// The pointers are borrowed - do NOT dispose them.
|
||||
/// </summary>
|
||||
public void GetItem(uint index,
|
||||
out NvttSurface* surface, out int face, out int mipmap,
|
||||
out NvttOutputOptions* outputOptions)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
NvttSurface* s;
|
||||
NvttOutputOptions* o;
|
||||
int f, m;
|
||||
Api.nvttBatchListGetItem(_ptr, index, &s, &f, &m, &o);
|
||||
surface = s;
|
||||
face = f;
|
||||
mipmap = m;
|
||||
outputOptions = o;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_ptr == null)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(NvttBatchListHandle));
|
||||
}
|
||||
}
|
||||
}
|
||||
123
src/ThridParty/Ghost.Nvtt/Wrapper/NvttCompressionOptions.cs
Normal file
123
src/ThridParty/Ghost.Nvtt/Wrapper/NvttCompressionOptions.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
namespace Ghost.Nvtt.Wrapper;
|
||||
|
||||
/// <summary>
|
||||
/// Controls how a surface is compressed - format, quality, pixel layout and
|
||||
/// optional quantization settings.
|
||||
/// </summary>
|
||||
public sealed unsafe class NvttCompressionOptionsHandle : IDisposable
|
||||
{
|
||||
private NvttCompressionOptions* _ptr;
|
||||
|
||||
/// <summary>Raw pointer - use only when calling the native API directly.</summary>
|
||||
public NvttCompressionOptions* Ptr => _ptr;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Construction / destruction
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public NvttCompressionOptionsHandle() => _ptr = Api.nvttCreateCompressionOptions();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_ptr != null)
|
||||
{
|
||||
Api.nvttDestroyCompressionOptions(_ptr);
|
||||
_ptr = null;
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Properties
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Target compressed format (e.g. BC1, BC7, ASTC …).</summary>
|
||||
public NvttFormat Format
|
||||
{
|
||||
set { ThrowIfDisposed(); Api.nvttSetCompressionOptionsFormat(_ptr, value); }
|
||||
}
|
||||
|
||||
/// <summary>Compression quality preset.</summary>
|
||||
public NvttQuality Quality
|
||||
{
|
||||
set { ThrowIfDisposed(); Api.nvttSetCompressionOptionsQuality(_ptr, value); }
|
||||
}
|
||||
|
||||
/// <summary>Pixel type for uncompressed RGB(A) output.</summary>
|
||||
public NvttPixelType PixelType
|
||||
{
|
||||
set { ThrowIfDisposed(); Api.nvttSetCompressionOptionsPixelType(_ptr, value); }
|
||||
}
|
||||
|
||||
/// <summary>Row-pitch alignment in bytes for uncompressed output.</summary>
|
||||
public int PitchAlignment
|
||||
{
|
||||
set { ThrowIfDisposed(); Api.nvttSetCompressionOptionsPitchAlignment(_ptr, value); }
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Resets all options to their default values.</summary>
|
||||
public void Reset()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Api.nvttResetCompressionOptions(_ptr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets per-channel importance weights used during block-compression error
|
||||
/// minimisation.
|
||||
/// </summary>
|
||||
public void SetColorWeights(float red, float green, float blue, float alpha = 1f)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Api.nvttSetCompressionOptionsColorWeights(_ptr, red, green, blue, alpha);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures a custom uncompressed pixel format by specifying the bit-depth
|
||||
/// and per-channel bit masks.
|
||||
/// </summary>
|
||||
public void SetPixelFormat(uint bitCount, uint rMask, uint gMask, uint bMask, uint aMask)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Api.nvttSetCompressionOptionsPixelFormat(_ptr, bitCount, rMask, gMask, bMask, aMask);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables dithering and binary-alpha quantisation.
|
||||
/// </summary>
|
||||
/// <param name="colorDithering">Dither RGB channels.</param>
|
||||
/// <param name="alphaDithering">Dither the alpha channel.</param>
|
||||
/// <param name="binaryAlpha">Snap alpha to 0 or 255.</param>
|
||||
/// <param name="alphaThreshold">Threshold used when <paramref name="binaryAlpha"/> is true.</param>
|
||||
public void SetQuantization(bool colorDithering, bool alphaDithering, bool binaryAlpha,
|
||||
int alphaThreshold = 127)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Api.nvttSetCompressionOptionsQuantization(
|
||||
_ptr,
|
||||
NvttInterop.ToNvtt(colorDithering),
|
||||
NvttInterop.ToNvtt(alphaDithering),
|
||||
NvttInterop.ToNvtt(binaryAlpha),
|
||||
alphaThreshold);
|
||||
}
|
||||
|
||||
/// <summary>Returns the D3D9 FourCC / D3DFORMAT value for the current settings.</summary>
|
||||
public uint GetD3D9Format()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return Api.nvttGetCompressionOptionsD3D9Format(_ptr);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_ptr == null)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(NvttCompressionOptionsHandle));
|
||||
}
|
||||
}
|
||||
}
|
||||
229
src/ThridParty/Ghost.Nvtt/Wrapper/NvttContext.cs
Normal file
229
src/ThridParty/Ghost.Nvtt/Wrapper/NvttContext.cs
Normal file
@@ -0,0 +1,229 @@
|
||||
namespace Ghost.Nvtt.Wrapper;
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper around the nvtt compression context — the central object that drives
|
||||
/// the compression pipeline.
|
||||
/// </summary>
|
||||
public sealed unsafe class NvttContextHandle : IDisposable
|
||||
{
|
||||
private NvttContext* _ptr;
|
||||
|
||||
/// <summary>Raw pointer - use only when calling the native API directly.</summary>
|
||||
public NvttContext* Ptr => _ptr;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Construction / destruction
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public NvttContextHandle() => _ptr = Api.nvttCreateContext();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_ptr != null)
|
||||
{
|
||||
Api.nvttDestroyContext(_ptr);
|
||||
_ptr = null;
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// CUDA acceleration
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Enables or disables CUDA-accelerated compression.</summary>
|
||||
public void SetCudaAcceleration(bool enable)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Api.nvttSetContextCudaAcceleration(_ptr, NvttInterop.ToNvtt(enable));
|
||||
}
|
||||
|
||||
/// <summary>Returns <c>true</c> if CUDA acceleration is currently enabled.</summary>
|
||||
public bool IsCudaAccelerationEnabled
|
||||
{
|
||||
get
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return NvttInterop.ToBool(Api.nvttContextIsCudaAccelerationEnabled(_ptr));
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Timing
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables internal timing collection.
|
||||
/// <paramref name="detailLevel"/> controls the granularity (0 = off, higher = more detail).
|
||||
/// </summary>
|
||||
public void EnableTiming(bool enable, int detailLevel = 1)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Api.nvttContextEnableTiming(_ptr, NvttInterop.ToNvtt(enable), detailLevel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the timing context owned by this nvtt context.
|
||||
/// The pointer is borrowed - do NOT dispose it separately.
|
||||
/// Returns <c>null</c> if timing was never enabled.
|
||||
/// </summary>
|
||||
public NvttTimingContext* GetTimingContextPtr()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return Api.nvttContextGetTimingContext(_ptr);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Estimate size
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Estimates the compressed size in bytes for <paramref name="mipmapCount"/>
|
||||
/// mip levels of <paramref name="img"/> using <paramref name="compressionOptions"/>.
|
||||
/// </summary>
|
||||
public int EstimateSize(NvttSurfaceHandle img, int mipmapCount,
|
||||
NvttCompressionOptionsHandle compressionOptions)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return Api.nvttContextEstimateSize(_ptr, img.Ptr, mipmapCount,
|
||||
compressionOptions.Ptr);
|
||||
}
|
||||
|
||||
/// <summary>Estimates the compressed size for a cube map.</summary>
|
||||
public int EstimateSizeCube(NvttCubeSurfaceHandle img, int mipmapCount,
|
||||
NvttCompressionOptionsHandle compressionOptions)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return Api.nvttContextEstimateSizeCube(_ptr, img.Ptr, mipmapCount,
|
||||
compressionOptions.Ptr);
|
||||
}
|
||||
|
||||
/// <summary>Estimates the compressed size for raw-data dimensions.</summary>
|
||||
public int EstimateSizeData(int w, int h, int d, int mipmapCount,
|
||||
NvttCompressionOptionsHandle compressionOptions)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return Api.nvttContextEstimateSizeData(_ptr, w, h, d, mipmapCount,
|
||||
compressionOptions.Ptr);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Output header
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Writes the DDS / KTX header to <paramref name="outputOptions"/>.
|
||||
/// Must be called once before compressing mip levels.
|
||||
/// Returns <c>false</c> on failure.
|
||||
/// </summary>
|
||||
public bool OutputHeader(NvttSurfaceHandle img, int mipmapCount,
|
||||
NvttCompressionOptionsHandle compressionOptions, NvttOutputOptionsHandle outputOptions)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return NvttInterop.ToBool(
|
||||
Api.nvttContextOutputHeader(_ptr, img.Ptr, mipmapCount,
|
||||
compressionOptions.Ptr, outputOptions.Ptr));
|
||||
}
|
||||
|
||||
/// <summary>Writes the header for a cube-map texture.</summary>
|
||||
public bool OutputHeaderCube(NvttCubeSurfaceHandle img, int mipmapCount,
|
||||
NvttCompressionOptionsHandle compressionOptions, NvttOutputOptionsHandle outputOptions)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return NvttInterop.ToBool(
|
||||
Api.nvttContextOutputHeaderCube(_ptr, img.Ptr, mipmapCount,
|
||||
compressionOptions.Ptr, outputOptions.Ptr));
|
||||
}
|
||||
|
||||
/// <summary>Writes the header using explicit dimensions instead of a surface.</summary>
|
||||
public bool OutputHeaderData(NvttTextureType type, int w, int h, int d,
|
||||
int mipmapCount, bool isNormalMap,
|
||||
NvttCompressionOptionsHandle compressionOptions, NvttOutputOptionsHandle outputOptions)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return NvttInterop.ToBool(
|
||||
Api.nvttContextOutputHeaderData(_ptr, type, w, h, d, mipmapCount,
|
||||
NvttInterop.ToNvtt(isNormalMap),
|
||||
compressionOptions.Ptr, outputOptions.Ptr));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Compress
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Compresses a single face/mip of <paramref name="img"/> and sends the
|
||||
/// result to <paramref name="outputOptions"/>.
|
||||
/// Returns <c>false</c> on failure.
|
||||
/// </summary>
|
||||
public bool Compress(NvttSurfaceHandle img, int face, int mipmap,
|
||||
NvttCompressionOptionsHandle compressionOptions, NvttOutputOptionsHandle outputOptions)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return NvttInterop.ToBool(
|
||||
Api.nvttContextCompress(_ptr, img.Ptr, face, mipmap,
|
||||
compressionOptions.Ptr, outputOptions.Ptr));
|
||||
}
|
||||
|
||||
/// <summary>Compresses a single mip of a cube-map face.</summary>
|
||||
public bool CompressCube(NvttCubeSurfaceHandle img, int mipmap,
|
||||
NvttCompressionOptionsHandle compressionOptions, NvttOutputOptionsHandle outputOptions)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return NvttInterop.ToBool(
|
||||
Api.nvttContextCompressCube(_ptr, img.Ptr, mipmap,
|
||||
compressionOptions.Ptr, outputOptions.Ptr));
|
||||
}
|
||||
|
||||
/// <summary>Compresses a single mip from a raw float RGBA buffer.</summary>
|
||||
public bool CompressData(int w, int h, int d, int face, int mipmap,
|
||||
ReadOnlySpan<float> rgba,
|
||||
NvttCompressionOptionsHandle compressionOptions, NvttOutputOptionsHandle outputOptions)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
fixed (float* p = rgba)
|
||||
{
|
||||
return NvttInterop.ToBool(
|
||||
Api.nvttContextCompressData(_ptr, w, h, d, face, mipmap, p,
|
||||
compressionOptions.Ptr, outputOptions.Ptr));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compresses a batch of (surface, face, mipmap, outputOptions) entries
|
||||
/// using the shared <paramref name="compressionOptions"/>.
|
||||
/// Returns <c>false</c> on failure.
|
||||
/// </summary>
|
||||
public bool CompressBatch(NvttBatchListHandle batchList,
|
||||
NvttCompressionOptionsHandle compressionOptions)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return NvttInterop.ToBool(
|
||||
Api.nvttContextCompressBatch(_ptr, batchList.Ptr,
|
||||
compressionOptions.Ptr));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Quantize
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Quantizes <paramref name="surface"/> in place according to
|
||||
/// <paramref name="compressionOptions"/> (useful before compressing
|
||||
/// to formats that only support limited bit depths).
|
||||
/// </summary>
|
||||
public void Quantize(NvttSurfaceHandle surface, NvttCompressionOptionsHandle compressionOptions)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Api.nvttContextQuantize(_ptr, surface.Ptr, compressionOptions.Ptr);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_ptr == null)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(NvttContextHandle));
|
||||
}
|
||||
}
|
||||
}
|
||||
227
src/ThridParty/Ghost.Nvtt/Wrapper/NvttCubeSurface.cs
Normal file
227
src/ThridParty/Ghost.Nvtt/Wrapper/NvttCubeSurface.cs
Normal file
@@ -0,0 +1,227 @@
|
||||
namespace Ghost.Nvtt.Wrapper;
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper around an nvtt cube-map surface (six faces, optional mip chain).
|
||||
///
|
||||
/// Methods that return a new <see cref="NvttCubeSurface"/> transfer ownership
|
||||
/// to the caller; dispose the result when done.
|
||||
/// </summary>
|
||||
public sealed unsafe class NvttCubeSurfaceHandle : IDisposable
|
||||
{
|
||||
private NvttCubeSurface* _ptr;
|
||||
|
||||
/// <summary>Raw pointer - use only when calling the native API directly.</summary>
|
||||
public NvttCubeSurface* Ptr => _ptr;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Construction / destruction
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public NvttCubeSurfaceHandle() => _ptr = Api.nvttCreateCubeSurface();
|
||||
|
||||
/// <summary>Wraps an existing raw pointer (takes ownership; will destroy on dispose).</summary>
|
||||
internal NvttCubeSurfaceHandle(NvttCubeSurface* existing) => _ptr = existing;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_ptr != null)
|
||||
{
|
||||
Api.nvttDestroyCubeSurface(_ptr);
|
||||
_ptr = null;
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Read-only properties
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Returns <c>true</c> when the cube surface holds no data.</summary>
|
||||
public bool IsNull
|
||||
{
|
||||
get { ThrowIfDisposed(); return NvttInterop.ToBool(Api.nvttCubeSurfaceIsNull(_ptr)); }
|
||||
}
|
||||
|
||||
/// <summary>Side length in pixels of each face.</summary>
|
||||
public int EdgeLength
|
||||
{
|
||||
get { ThrowIfDisposed(); return Api.nvttCubeSurfaceEdgeLength(_ptr); }
|
||||
}
|
||||
|
||||
/// <summary>Number of mip levels stored in this cube surface.</summary>
|
||||
public int MipmapCount
|
||||
{
|
||||
get { ThrowIfDisposed(); return Api.nvttCubeSurfaceCountMipmaps(_ptr); }
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Load / Save
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Loads a cube map from disk.
|
||||
/// <paramref name="mipmap"/> selects which mip level to load (-1 = all).
|
||||
/// Returns <c>false</c> on failure.
|
||||
/// </summary>
|
||||
public bool Load(string fileName, int mipmap = 0)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Span<byte> buf = stackalloc byte[NvttInterop._MAX_STACK_PATH];
|
||||
var utf8 = NvttInterop.ToUtf8(fileName, buf);
|
||||
fixed (byte* p = utf8)
|
||||
{
|
||||
return NvttInterop.ToBool(Api.nvttCubeSurfaceLoad(_ptr, (sbyte*)p, mipmap));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Loads a cube map from a managed byte array. Returns <c>false</c> on failure.</summary>
|
||||
public bool LoadFromMemory(ReadOnlySpan<byte> data, int mipmap = 0)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
fixed (byte* p = data)
|
||||
{
|
||||
return NvttInterop.ToBool(
|
||||
Api.nvttCubeSurfaceLoadFromMemory(_ptr, p, (ulong)data.Length, mipmap));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Saves the cube map to disk. Returns <c>false</c> on failure.</summary>
|
||||
public bool Save(string fileName)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Span<byte> buf = stackalloc byte[NvttInterop._MAX_STACK_PATH];
|
||||
var utf8 = NvttInterop.ToUtf8(fileName, buf);
|
||||
fixed (byte* p = utf8)
|
||||
{
|
||||
return NvttInterop.ToBool(Api.nvttCubeSurfaceSave(_ptr, (sbyte*)p));
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Face access
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Returns the raw <see cref="NvttSurface"/> pointer for the given face
|
||||
/// (0–5). The pointer is owned by this cube surface - do NOT dispose it.
|
||||
/// </summary>
|
||||
public NvttSurface* FacePtr(int face)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return Api.nvttCubeSurfaceFace(_ptr, face);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Fold / Unfold
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Folds a cross-layout <see cref="NvttSurface"/> into this cube surface.
|
||||
/// </summary>
|
||||
public void Fold(NvttSurfaceHandle img, NvttCubeLayout layout)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Api.nvttCubeSurfaceFold(_ptr, img.Ptr, layout);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unfolds the cube surface into a flat cross-layout image.
|
||||
/// Caller owns the returned surface.
|
||||
/// </summary>
|
||||
public NvttSurfaceHandle Unfold(NvttCubeLayout layout)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return new NvttSurfaceHandle(Api.nvttCubeSurfaceUnfold(_ptr, layout));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Query methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Returns the per-channel average for the given channel index.</summary>
|
||||
public float Average(int channel)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return Api.nvttCubeSurfaceAverage(_ptr, channel);
|
||||
}
|
||||
|
||||
/// <summary>Returns the min and max values of a channel across all faces.</summary>
|
||||
public void Range(int channel, out float min, out float max)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
float lo, hi;
|
||||
Api.nvttCubeSurfaceRange(_ptr, channel, &lo, &hi);
|
||||
min = lo;
|
||||
max = hi;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Pixel operations
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Clamps a channel to [<paramref name="low"/>, <paramref name="high"/>].</summary>
|
||||
public void Clamp(int channel, float low, float high)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Api.nvttCubeSurfaceClamp(_ptr, channel, low, high);
|
||||
}
|
||||
|
||||
/// <summary>Applies gamma expansion (toLinear) to all faces.</summary>
|
||||
public void ToLinear(float gamma)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Api.nvttCubeSurfaceToLinear(_ptr, gamma);
|
||||
}
|
||||
|
||||
/// <summary>Applies gamma compression to all faces.</summary>
|
||||
public void ToGamma(float gamma)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Api.nvttCubeSurfaceToGamma(_ptr, gamma);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Filtering (return new owned NvttCubeSurface)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Computes an irradiance-filtered cube map of the given <paramref name="size"/>.
|
||||
/// Caller owns the result.
|
||||
/// </summary>
|
||||
public NvttCubeSurfaceHandle IrradianceFilter(int size, EdgeFixup fixup = EdgeFixup.NVTT_EdgeFixup_None)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return new NvttCubeSurfaceHandle(Api.nvttCubeSurfaceIrradianceFilter(_ptr, size, fixup));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes a cosine-power (specular) filtered cube map.
|
||||
/// Caller owns the result.
|
||||
/// </summary>
|
||||
public NvttCubeSurfaceHandle CosinePowerFilter(int size, float cosinePower,
|
||||
EdgeFixup fixup = EdgeFixup.NVTT_EdgeFixup_None)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return new NvttCubeSurfaceHandle(
|
||||
Api.nvttCubeSurfaceCosinePowerFilter(_ptr, size, cosinePower, fixup));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resamples the cube map to the given <paramref name="size"/> using fast bilinear resampling.
|
||||
/// Caller owns the result.
|
||||
/// </summary>
|
||||
public NvttCubeSurfaceHandle FastResample(int size, EdgeFixup fixup = EdgeFixup.NVTT_EdgeFixup_None)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return new NvttCubeSurfaceHandle(Api.nvttCubeSurfaceFastResample(_ptr, size, fixup));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_ptr == null)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(NvttCubeSurfaceHandle));
|
||||
}
|
||||
}
|
||||
}
|
||||
183
src/ThridParty/Ghost.Nvtt/Wrapper/NvttGlobal.cs
Normal file
183
src/ThridParty/Ghost.Nvtt/Wrapper/NvttGlobal.cs
Normal file
@@ -0,0 +1,183 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.Nvtt.Wrapper;
|
||||
|
||||
/// <summary>
|
||||
/// Static helpers wrapping global nvtt functions (version, CUDA detection,
|
||||
/// error utilities, image comparison, mipmap helpers).
|
||||
/// </summary>
|
||||
public static unsafe class NvttGlobal
|
||||
{
|
||||
// -------------------------------------------------------------------------
|
||||
// Library info
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Returns the nvtt library version as a packed uint (major*10000 + minor*100 + patch).</summary>
|
||||
public static uint Version => Api.nvttVersion();
|
||||
|
||||
/// <summary>Returns <c>true</c> when a CUDA-capable GPU is available.</summary>
|
||||
public static bool IsCudaSupported
|
||||
=> NvttInterop.ToBool(Api.nvttIsCudaSupported());
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Error strings
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Returns a human-readable string for <paramref name="error"/>.</summary>
|
||||
public static string? ErrorString(NvttError error)
|
||||
=> NvttInterop.FromUtf8(Api.nvttErrorString(error));
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Global message callback
|
||||
//
|
||||
// The delegate type must be kept alive as long as the callback is registered.
|
||||
// Store the returned token and dispose it to clear the callback.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Delegate type for the global nvtt message callback.
|
||||
/// </summary>
|
||||
public delegate void MessageCallback(
|
||||
NvttSeverity severity, NvttError error, string? description);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
internal delegate void NativeMessageCallback(
|
||||
NvttSeverity severity, NvttError error, sbyte* description, void* userData);
|
||||
|
||||
/// <summary>
|
||||
/// A registration token returned by <see cref="SetMessageCallback"/>.
|
||||
/// Dispose to clear the callback and release the pinned delegate.
|
||||
/// </summary>
|
||||
public sealed class MessageCallbackToken : IDisposable
|
||||
{
|
||||
private NativeMessageCallback? _pinned;
|
||||
|
||||
internal MessageCallbackToken(NativeMessageCallback pinned)
|
||||
=> _pinned = pinned;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_pinned != null)
|
||||
{
|
||||
// Clear the callback by registering null.
|
||||
Api.nvttSetMessageCallback(null, null);
|
||||
_pinned = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a managed message callback that nvtt calls for warnings and errors.
|
||||
/// Returns a token; dispose the token to unregister.
|
||||
/// </summary>
|
||||
public static MessageCallbackToken SetMessageCallback(MessageCallback callback)
|
||||
{
|
||||
void native(NvttSeverity sev, NvttError err, sbyte* descPtr, void* _)
|
||||
{
|
||||
var desc = NvttInterop.FromUtf8(descPtr);
|
||||
callback(sev, err, desc);
|
||||
}
|
||||
|
||||
var fp = Marshal.GetFunctionPointerForDelegate(native);
|
||||
Api.nvttSetMessageCallback(
|
||||
(delegate* unmanaged[Cdecl]<NvttSeverity, NvttError, sbyte*, void*, void>)fp,
|
||||
null);
|
||||
|
||||
return new MessageCallbackToken(native);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Image comparison (error metrics)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>RMS per-channel colour error between two surfaces.</summary>
|
||||
public static float RmsError(NvttSurfaceHandle reference, NvttSurfaceHandle img,
|
||||
NvttTimingContext* tc = null)
|
||||
=> Api.nvttRmsError(reference.Ptr, img.Ptr, tc);
|
||||
|
||||
/// <summary>RMS alpha-channel error between two surfaces.</summary>
|
||||
public static float RmsAlphaError(NvttSurfaceHandle reference, NvttSurfaceHandle img,
|
||||
NvttTimingContext* tc = null)
|
||||
=> Api.nvttRmsAlphaError(reference.Ptr, img.Ptr, tc);
|
||||
|
||||
/// <summary>RMS CIE-Lab perceptual error between two surfaces.</summary>
|
||||
public static float RmsCIELabError(NvttSurfaceHandle reference, NvttSurfaceHandle img,
|
||||
NvttTimingContext* tc = null)
|
||||
=> Api.nvttRmsCIELabError(reference.Ptr, img.Ptr, tc);
|
||||
|
||||
/// <summary>Angular error between two normal-map surfaces.</summary>
|
||||
public static float AngularError(NvttSurfaceHandle reference, NvttSurfaceHandle img,
|
||||
NvttTimingContext* tc = null)
|
||||
=> Api.nvttAngularError(reference.Ptr, img.Ptr, tc);
|
||||
|
||||
/// <summary>
|
||||
/// Tone-mapped RMS error. Useful for HDR comparisons.
|
||||
/// </summary>
|
||||
public static float RmsToneMappedError(NvttSurfaceHandle reference, NvttSurfaceHandle img,
|
||||
float exposure, NvttTimingContext* tc = null)
|
||||
=> Api.nvttRmsToneMappedError(reference.Ptr, img.Ptr, exposure, tc);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Difference image
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new surface containing the scaled per-pixel difference.
|
||||
/// Caller owns the returned surface.
|
||||
/// </summary>
|
||||
public static NvttSurfaceHandle Diff(NvttSurfaceHandle reference, NvttSurfaceHandle img,
|
||||
float scale, NvttTimingContext* tc = null)
|
||||
=> new NvttSurfaceHandle(Api.nvttDiff(reference.Ptr, img.Ptr, scale, tc));
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Histogram
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new surface containing a histogram visualisation of
|
||||
/// <paramref name="img"/> at the given dimensions.
|
||||
/// Caller owns the returned surface.
|
||||
/// </summary>
|
||||
public static NvttSurfaceHandle Histogram(NvttSurfaceHandle img, int width, int height,
|
||||
NvttTimingContext* tc = null)
|
||||
=> new NvttSurfaceHandle(Api.nvttHistogram(img.Ptr, width, height, tc));
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new surface containing a histogram visualisation with an
|
||||
/// explicit value range.
|
||||
/// Caller owns the returned surface.
|
||||
/// </summary>
|
||||
public static NvttSurfaceHandle HistogramRange(NvttSurfaceHandle img,
|
||||
float minRange, float maxRange, int width, int height,
|
||||
NvttTimingContext* tc = null)
|
||||
=> new NvttSurfaceHandle(
|
||||
Api.nvttHistogramRange(img.Ptr, minRange, maxRange, width, height, tc));
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Extent / mipmap helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Computes the target extent (power-of-two rounding, texture type clamping,
|
||||
/// etc.) for a texture of the given dimensions.
|
||||
/// Modifies <paramref name="width"/>, <paramref name="height"/> and
|
||||
/// <paramref name="depth"/> in place.
|
||||
/// </summary>
|
||||
public static void GetTargetExtent(ref int width, ref int height, ref int depth,
|
||||
int maxExtent, NvttRoundMode roundMode, NvttTextureType textureType,
|
||||
NvttTimingContext* tc = null)
|
||||
{
|
||||
fixed (int* pw = &width, ph = &height, pd = &depth)
|
||||
{
|
||||
Api.nvttGetTargetExtent(pw, ph, pd, maxExtent, roundMode, textureType, tc);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of mip levels that can be generated for the given
|
||||
/// base dimensions.
|
||||
/// </summary>
|
||||
public static int CountMipmaps(int w, int h, int d,
|
||||
NvttTimingContext* tc = null)
|
||||
=> Api.nvttCountMipmaps(w, h, d, tc);
|
||||
}
|
||||
67
src/ThridParty/Ghost.Nvtt/Wrapper/NvttInterop.cs
Normal file
67
src/ThridParty/Ghost.Nvtt/Wrapper/NvttInterop.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Ghost.Nvtt.Wrapper;
|
||||
|
||||
/// <summary>
|
||||
/// Internal helpers for converting between managed and unmanaged types.
|
||||
/// </summary>
|
||||
internal static unsafe class NvttInterop
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static NvttBoolean ToNvtt(bool value)
|
||||
=> value ? NvttBoolean.NVTT_True : NvttBoolean.NVTT_False;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static bool ToBool(NvttBoolean value)
|
||||
=> value != NvttBoolean.NVTT_False;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// String to UTF-8 sbyte*
|
||||
//
|
||||
// Usage pattern:
|
||||
// fixed (byte* ptr = NvttInterop.ToUtf8(str, stackalloc byte[MaxStack]))
|
||||
// Api.nvttSomething((sbyte*)ptr);
|
||||
//
|
||||
// For paths longer than MaxStack bytes the helper falls back to a heap
|
||||
// allocation via Encoding.UTF8.GetBytes. The Span<byte> overload lets the
|
||||
// caller decide the stackalloc size.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
internal const int _MAX_STACK_PATH = 512;
|
||||
|
||||
/// <summary>
|
||||
/// Encode <paramref name="value"/> as null-terminated UTF-8 into
|
||||
/// <paramref name="buffer"/>. Returns the used portion (including the
|
||||
/// null terminator). If the buffer is too small a new heap array is
|
||||
/// returned instead.
|
||||
/// </summary>
|
||||
internal static Span<byte> ToUtf8(string value, Span<byte> buffer)
|
||||
{
|
||||
var needed = Encoding.UTF8.GetByteCount(value) + 1; // +1 for null term
|
||||
if (needed > buffer.Length)
|
||||
{
|
||||
buffer = new byte[needed];
|
||||
}
|
||||
|
||||
var written = Encoding.UTF8.GetBytes(value, buffer);
|
||||
buffer[written] = 0; // null terminator
|
||||
return buffer[..(written + 1)];
|
||||
}
|
||||
|
||||
internal static string? FromUtf8(sbyte* ptr)
|
||||
{
|
||||
if (ptr == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var len = 0;
|
||||
while (ptr[len] != 0)
|
||||
{
|
||||
len++;
|
||||
}
|
||||
|
||||
return Encoding.UTF8.GetString((byte*)ptr, len);
|
||||
}
|
||||
}
|
||||
210
src/ThridParty/Ghost.Nvtt/Wrapper/NvttOutputOptions.cs
Normal file
210
src/ThridParty/Ghost.Nvtt/Wrapper/NvttOutputOptions.cs
Normal file
@@ -0,0 +1,210 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.Nvtt.Wrapper;
|
||||
|
||||
/// <summary>
|
||||
/// Configures where compressed data is written and how it is formatted.
|
||||
///
|
||||
/// Managed callback delegates are stored in this object and passed to the
|
||||
/// native library via <see cref="Marshal.GetFunctionPointerForDelegate"/>.
|
||||
/// The delegates are kept alive by pinned <see cref="GCHandle"/> instances
|
||||
/// for the lifetime of this object.
|
||||
/// </summary>
|
||||
public sealed unsafe class NvttOutputOptionsHandle : IDisposable
|
||||
{
|
||||
private NvttOutputOptions* _ptr;
|
||||
|
||||
/// <summary>Raw pointer - use only when calling the native API directly.</summary>
|
||||
public NvttOutputOptions* Ptr => _ptr;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Managed callback storage
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// Delegate types that match the native C signatures exactly.
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate void BeginImageDelegate(int size, int width, int height, int depth, int face, int mipLevel);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate NvttBoolean OutputDataDelegate(void* data, int size, NvttBoolean lastChunk);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate void ErrorDelegate(NvttError error);
|
||||
|
||||
// Pinned delegate instances - must stay alive as long as native code may call them.
|
||||
private BeginImageDelegate? _beginImageDelegate;
|
||||
private OutputDataDelegate? _outputDataDelegate;
|
||||
private ErrorDelegate? _errorDelegate;
|
||||
|
||||
// Managed user-facing callbacks.
|
||||
private Action<int, int, int, int, int, int>? _beginImage;
|
||||
private Func<nint, int, bool>? _outputData;
|
||||
private Action? _endImage;
|
||||
private Action<NvttError>? _errorHandler;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Construction / destruction
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public NvttOutputOptionsHandle() => _ptr = Api.nvttCreateOutputOptions();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_ptr != null)
|
||||
{
|
||||
Api.nvttDestroyOutputOptions(_ptr);
|
||||
_ptr = null;
|
||||
}
|
||||
// Release delegate references so GC can collect them.
|
||||
_beginImageDelegate = null;
|
||||
_outputDataDelegate = null;
|
||||
_errorDelegate = null;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Properties
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Path of the output file. The file is created/overwritten when
|
||||
/// compression runs.
|
||||
/// </summary>
|
||||
public string FileName
|
||||
{
|
||||
set
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Span<byte> buf = stackalloc byte[NvttInterop._MAX_STACK_PATH];
|
||||
var utf8 = NvttInterop.ToUtf8(value, buf);
|
||||
fixed (byte* p = utf8)
|
||||
{
|
||||
Api.nvttSetOutputOptionsFileName(_ptr, (sbyte*)p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether to write a DDS / KTX file header. Default is <c>true</c>.
|
||||
/// </summary>
|
||||
public bool OutputHeader
|
||||
{
|
||||
set { ThrowIfDisposed(); Api.nvttSetOutputOptionsOutputHeader(_ptr, NvttInterop.ToNvtt(value)); }
|
||||
}
|
||||
|
||||
/// <summary>Container format (DDS or DDS10).</summary>
|
||||
public NvttContainer Container
|
||||
{
|
||||
set { ThrowIfDisposed(); Api.nvttSetOutputOptionsContainer(_ptr, value); }
|
||||
}
|
||||
|
||||
/// <summary>Application-defined version number stored in the header.</summary>
|
||||
public int UserVersion
|
||||
{
|
||||
set { ThrowIfDisposed(); Api.nvttSetOutputOptionsUserVersion(_ptr, value); }
|
||||
}
|
||||
|
||||
/// <summary>Sets the sRGB flag in the output header.</summary>
|
||||
public bool Srgb
|
||||
{
|
||||
set { ThrowIfDisposed(); Api.nvttSetOutputOptionsSrgbFlag(_ptr, NvttInterop.ToNvtt(value)); }
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Resets all options to their default values.</summary>
|
||||
public void Reset()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Api.nvttResetOutputOptions(_ptr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Installs managed callbacks that receive the compressed data stream.
|
||||
///
|
||||
/// <para><paramref name="beginImage"/> is called once per mip level before any
|
||||
/// data arrives: <c>(size, width, height, depth, face, mipLevel)</c>.</para>
|
||||
/// <para><paramref name="outputData"/> receives each chunk of compressed bytes.
|
||||
/// The first argument is an <see cref="nint"/> pointing to the data, the
|
||||
/// second is the byte count. Return <c>true</c> to continue, <c>false</c>
|
||||
/// to abort.</para>
|
||||
/// <para><paramref name="endImage"/> is called once after the last chunk.</para>
|
||||
///
|
||||
/// Only one set of output callbacks can be active at a time; calling this
|
||||
/// method again replaces the previous ones.
|
||||
/// </summary>
|
||||
public void SetOutputHandler(
|
||||
Action<int, int, int, int, int, int>? beginImage,
|
||||
Func<nint, int, bool>? outputData,
|
||||
Action? endImage)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_beginImage = beginImage;
|
||||
_outputData = outputData;
|
||||
_endImage = endImage;
|
||||
RebindOutputHandler();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Installs a managed error handler. The handler is invoked with the
|
||||
/// <see cref="NvttError"/> code whenever the native library encounters an
|
||||
/// error.
|
||||
/// </summary>
|
||||
public void SetErrorHandler(Action<NvttError>? handler)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_errorHandler = handler;
|
||||
RebindErrorHandler();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Internal delegate trampolines (instance-bound via closures)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private void RebindOutputHandler()
|
||||
{
|
||||
// Capture current callbacks into local vars for the closure.
|
||||
var beginImage = _beginImage;
|
||||
var outputData = _outputData;
|
||||
|
||||
_beginImageDelegate = (size, width, height, depth, face, mipLevel) =>
|
||||
beginImage?.Invoke(size, width, height, depth, face, mipLevel);
|
||||
|
||||
_outputDataDelegate = (data, size, lastChunk) =>
|
||||
{
|
||||
var ok = outputData?.Invoke((nint)data, size) ?? true;
|
||||
return ok ? Ghost.Nvtt.NvttBoolean.NVTT_True : Ghost.Nvtt.NvttBoolean.NVTT_False;
|
||||
};
|
||||
|
||||
var beginPtr = Marshal.GetFunctionPointerForDelegate(_beginImageDelegate);
|
||||
var outputPtr = Marshal.GetFunctionPointerForDelegate(_outputDataDelegate);
|
||||
|
||||
Api.nvttSetOutputOptionsOutputHandler(
|
||||
_ptr,
|
||||
(delegate* unmanaged[Cdecl]<int, int, int, int, int, int, void>)beginPtr,
|
||||
(delegate* unmanaged[Cdecl]<void*, int, NvttBoolean>)outputPtr,
|
||||
IntPtr.Zero);
|
||||
}
|
||||
|
||||
private void RebindErrorHandler()
|
||||
{
|
||||
var handler = _errorHandler;
|
||||
_errorDelegate = error => handler?.Invoke(error);
|
||||
|
||||
var errorPtr = Marshal.GetFunctionPointerForDelegate(_errorDelegate);
|
||||
Api.nvttSetOutputOptionsErrorHandler(
|
||||
_ptr,
|
||||
(delegate* unmanaged[Cdecl]<NvttError, void>)errorPtr);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_ptr == null)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(NvttOutputOptionsHandle));
|
||||
}
|
||||
}
|
||||
}
|
||||
712
src/ThridParty/Ghost.Nvtt/Wrapper/NvttSurface.cs
Normal file
712
src/ThridParty/Ghost.Nvtt/Wrapper/NvttSurface.cs
Normal file
@@ -0,0 +1,712 @@
|
||||
namespace Ghost.Nvtt.Wrapper;
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper around a single 2-D / 3-D / cube-face image surface used as input
|
||||
/// to the compression pipeline.
|
||||
///
|
||||
/// Most mutating methods accept an optional <paramref name="tc"/> timing
|
||||
/// context. Pass <c>null</c> (the default) to skip timing.
|
||||
/// </summary>
|
||||
public sealed unsafe class NvttSurfaceHandle : IDisposable
|
||||
{
|
||||
private NvttSurface* _ptr;
|
||||
|
||||
/// <summary>Raw pointer - use only when calling the native API directly.</summary>
|
||||
public NvttSurface* Ptr => _ptr;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Construction / destruction
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public NvttSurfaceHandle() => _ptr = Api.nvttCreateSurface();
|
||||
|
||||
/// <summary>Wraps an existing raw pointer (takes ownership; will destroy on dispose).</summary>
|
||||
internal NvttSurfaceHandle(NvttSurface* existing) => _ptr = existing;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_ptr != null)
|
||||
{
|
||||
Api.nvttDestroySurface(_ptr);
|
||||
_ptr = null;
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Clone / sub-image
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Returns a deep copy of this surface.</summary>
|
||||
public NvttSurfaceHandle Clone()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return new NvttSurfaceHandle(Api.nvttSurfaceClone(_ptr));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts a rectangular sub-region into a new <see cref="NvttSurfaceHandle"/>.
|
||||
/// </summary>
|
||||
public NvttSurfaceHandle CreateSubImage(
|
||||
int x0, int x1, int y0, int y1, int z0, int z1,
|
||||
NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return new NvttSurfaceHandle(Api.nvttSurfaceCreateSubImage(_ptr, x0, x1, y0, y1, z0, z1, tc));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Read-only properties
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Returns <c>true</c> when the surface holds no data.</summary>
|
||||
public bool IsNull
|
||||
{
|
||||
get { ThrowIfDisposed(); return NvttInterop.ToBool(Api.nvttSurfaceIsNull(_ptr)); }
|
||||
}
|
||||
|
||||
/// <summary>Image width in pixels.</summary>
|
||||
public int Width
|
||||
{
|
||||
get { ThrowIfDisposed(); return Api.nvttSurfaceWidth(_ptr); }
|
||||
}
|
||||
|
||||
/// <summary>Image height in pixels.</summary>
|
||||
public int Height
|
||||
{
|
||||
get { ThrowIfDisposed(); return Api.nvttSurfaceHeight(_ptr); }
|
||||
}
|
||||
|
||||
/// <summary>Image depth (1 for 2-D textures).</summary>
|
||||
public int Depth
|
||||
{
|
||||
get { ThrowIfDisposed(); return Api.nvttSurfaceDepth(_ptr); }
|
||||
}
|
||||
|
||||
/// <summary>Texture dimensionality.</summary>
|
||||
public NvttTextureType TextureType
|
||||
{
|
||||
get { ThrowIfDisposed(); return Api.nvttSurfaceType(_ptr); }
|
||||
}
|
||||
|
||||
/// <summary>Whether the surface contains a normal map.</summary>
|
||||
public bool IsNormalMap
|
||||
{
|
||||
get { ThrowIfDisposed(); return NvttInterop.ToBool(Api.nvttSurfaceIsNormalMap(_ptr)); }
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Settable properties
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>UV wrap mode used when filtering near edges.</summary>
|
||||
public NvttWrapMode WrapMode
|
||||
{
|
||||
get { ThrowIfDisposed(); return Api.nvttSurfaceWrapMode(_ptr); }
|
||||
}
|
||||
|
||||
/// <summary>Alpha mode interpretation.</summary>
|
||||
public NvttAlphaMode AlphaMode
|
||||
{
|
||||
get { ThrowIfDisposed(); return Api.nvttSurfaceAlphaMode(_ptr); }
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Query methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of mip levels that can be generated down to
|
||||
/// <paramref name="minSize"/> pixels on the smallest side.
|
||||
/// </summary>
|
||||
public int CountMipmaps(int minSize = 1)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return Api.nvttSurfaceCountMipmaps(_ptr, minSize);
|
||||
}
|
||||
|
||||
/// <summary>Alpha-test coverage for the given reference value and channel.</summary>
|
||||
public float AlphaTestCoverage(float alphaRef, int alphaChannel = 3)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return Api.nvttSurfaceAlphaTestCoverage(_ptr, alphaRef, alphaChannel);
|
||||
}
|
||||
|
||||
/// <summary>Per-channel average luminance.</summary>
|
||||
public float Average(int channel, int alphaChannel = 3, float gamma = 2.2f)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return Api.nvttSurfaceAverage(_ptr, channel, alphaChannel, gamma);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a pointer to the raw float data for all four channels interleaved.
|
||||
/// The span is valid only while this surface is alive.
|
||||
/// </summary>
|
||||
public Span<float> Data
|
||||
{
|
||||
get
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
var p = Api.nvttSurfaceData(_ptr);
|
||||
var count = Width * Height * Depth * 4;
|
||||
return p == null ? Span<float>.Empty : new Span<float>(p, count);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a pointer to the raw float data for a single channel (0=R,1=G,2=B,3=A).
|
||||
/// The span is valid only while this surface is alive.
|
||||
/// </summary>
|
||||
public Span<float> Channel(int index)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
var p = Api.nvttSurfaceChannel(_ptr, index);
|
||||
var count = Width * Height * Depth;
|
||||
return p == null ? Span<float>.Empty : new Span<float>(p, count);
|
||||
}
|
||||
|
||||
/// <summary>Populates a histogram array for the given channel.</summary>
|
||||
public void Histogram(int channel, float rangeMin, float rangeMax, Span<int> bins,
|
||||
NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
fixed (int* b = bins)
|
||||
{
|
||||
Api.nvttSurfaceHistogram(_ptr, channel, rangeMin, rangeMax, bins.Length, b, tc);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Returns the minimum and maximum values of a channel.</summary>
|
||||
public void Range(int channel, out float min, out float max,
|
||||
int alphaChannel = 3, float alphaRef = 0f,
|
||||
NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
float lo, hi;
|
||||
Api.nvttSurfaceRange(_ptr, channel, &lo, &hi, alphaChannel, alphaRef, tc);
|
||||
min = lo;
|
||||
max = hi;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Load / Save
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Loads an image from disk. Returns <c>false</c> on failure.</summary>
|
||||
public bool Load(string fileName, out bool hasAlpha, bool expectSigned = false,
|
||||
NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Span<byte> buf = stackalloc byte[NvttInterop._MAX_STACK_PATH];
|
||||
var utf8 = NvttInterop.ToUtf8(fileName, buf);
|
||||
fixed (byte* p = utf8)
|
||||
{
|
||||
NvttBoolean nvAlpha;
|
||||
var ok = NvttInterop.ToBool(
|
||||
Api.nvttSurfaceLoad(_ptr, (sbyte*)p, &nvAlpha,
|
||||
NvttInterop.ToNvtt(expectSigned), tc));
|
||||
hasAlpha = NvttInterop.ToBool(nvAlpha);
|
||||
return ok;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Loads an image from a managed byte array. Returns <c>false</c> on failure.</summary>
|
||||
public bool LoadFromMemory(ReadOnlySpan<byte> data, out bool hasAlpha,
|
||||
bool expectSigned = false, NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
fixed (byte* p = data)
|
||||
{
|
||||
NvttBoolean nvAlpha;
|
||||
var ok = NvttInterop.ToBool(
|
||||
Api.nvttSurfaceLoadFromMemory(_ptr, p, (ulong)data.Length,
|
||||
&nvAlpha, NvttInterop.ToNvtt(expectSigned), tc));
|
||||
hasAlpha = NvttInterop.ToBool(nvAlpha);
|
||||
return ok;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Saves the surface to disk. Returns <c>false</c> on failure.</summary>
|
||||
public bool Save(string fileName, bool hasAlpha = false, bool hdr = false,
|
||||
NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Span<byte> buf = stackalloc byte[NvttInterop._MAX_STACK_PATH];
|
||||
var utf8 = NvttInterop.ToUtf8(fileName, buf);
|
||||
fixed (byte* p = utf8)
|
||||
{
|
||||
return NvttInterop.ToBool(
|
||||
Api.nvttSurfaceSave(_ptr, (sbyte*)p,
|
||||
NvttInterop.ToNvtt(hasAlpha), NvttInterop.ToNvtt(hdr), tc));
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Set image data
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Allocates an empty surface of the given dimensions.</summary>
|
||||
public bool SetImage(int w, int h, int d = 1, NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return NvttInterop.ToBool(Api.nvttSurfaceSetImage(_ptr, w, h, d, tc));
|
||||
}
|
||||
|
||||
/// <summary>Sets the surface from interleaved RGBA data.</summary>
|
||||
public bool SetImageData(NvttInputFormat format, int w, int h, int d,
|
||||
ReadOnlySpan<byte> data, bool unsignedToSigned = false,
|
||||
NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
fixed (byte* p = data)
|
||||
{
|
||||
return NvttInterop.ToBool(
|
||||
Api.nvttSurfaceSetImageData(_ptr, format, w, h, d, p,
|
||||
NvttInterop.ToNvtt(unsignedToSigned), tc));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Sets the surface from separate RGBA channel planes.</summary>
|
||||
public bool SetImageRGBA(NvttInputFormat format, int w, int h, int d,
|
||||
ReadOnlySpan<byte> r, ReadOnlySpan<byte> g,
|
||||
ReadOnlySpan<byte> b, ReadOnlySpan<byte> a,
|
||||
NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
fixed (byte* pr = r, pg = g, pb = b, pa = a)
|
||||
{
|
||||
return NvttInterop.ToBool(
|
||||
Api.nvttSurfaceSetImageRGBA(_ptr, format, w, h, d, pr, pg, pb, pa, tc));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Sets the surface from a compressed 2-D image.</summary>
|
||||
public bool SetImage2D(NvttFormat format, int w, int h,
|
||||
ReadOnlySpan<byte> data, NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
fixed (byte* p = data)
|
||||
{
|
||||
return NvttInterop.ToBool(
|
||||
Api.nvttSurfaceSetImage2D(_ptr, format, w, h, p, tc));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Sets the surface from a compressed 3-D image.</summary>
|
||||
public bool SetImage3D(NvttFormat format, int w, int h, int d,
|
||||
ReadOnlySpan<byte> data, NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
fixed (byte* p = data)
|
||||
{
|
||||
return NvttInterop.ToBool(
|
||||
Api.nvttSurfaceSetImage3D(_ptr, format, w, h, d, p, tc));
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Resize / mipmap
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Resizes the surface to the exact dimensions given.</summary>
|
||||
public void Resize(int w, int h, int d, NvttResizeFilter filter,
|
||||
float filterWidth = 1f, NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Api.nvttSurfaceResize(_ptr, w, h, d, filter, filterWidth, null, tc);
|
||||
}
|
||||
|
||||
/// <summary>Resizes so that the longest extent is at most <paramref name="maxExtent"/>.</summary>
|
||||
public void ResizeMax(int maxExtent, NvttRoundMode mode, NvttResizeFilter filter,
|
||||
NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Api.nvttSurfaceResizeMax(_ptr, maxExtent, mode, filter, tc);
|
||||
}
|
||||
|
||||
/// <summary>Resizes to a square texture with side at most <paramref name="maxExtent"/>.</summary>
|
||||
public void ResizeMakeSquare(int maxExtent, NvttRoundMode mode, NvttResizeFilter filter,
|
||||
NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Api.nvttSurfaceResizeMakeSquare(_ptr, maxExtent, mode, filter, tc);
|
||||
}
|
||||
|
||||
/// <summary>Pads or crops the canvas to the given dimensions without resampling.</summary>
|
||||
public void CanvasSize(int w, int h, int d, NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Api.nvttSurfaceCanvasSize(_ptr, w, h, d, tc);
|
||||
}
|
||||
|
||||
/// <summary>Returns <c>true</c> if a next mip level can still be generated.</summary>
|
||||
public bool CanMakeNextMipmap(int minSize = 1)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return NvttInterop.ToBool(Api.nvttSurfaceCanMakeNextMipmap(_ptr, minSize));
|
||||
}
|
||||
|
||||
/// <summary>Generates the next mip level in place (downsamples by 2).</summary>
|
||||
public bool BuildNextMipmap(NvttMipmapFilter filter, int minSize = 1,
|
||||
NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return NvttInterop.ToBool(
|
||||
Api.nvttSurfaceBuildNextMipmapDefaults(_ptr, filter, minSize, tc));
|
||||
}
|
||||
|
||||
/// <summary>Generates the next mip level using a solid colour.</summary>
|
||||
public bool BuildNextMipmapSolidColor(float r, float g, float b, float a,
|
||||
NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
var color = stackalloc float[4] { r, g, b, a };
|
||||
return NvttInterop.ToBool(
|
||||
Api.nvttSurfaceBuildNextMipmapSolidColor(_ptr, color, tc));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Colour-space conversions
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Converts from sRGB to linear (per-channel).</summary>
|
||||
public void ToLinearFromSrgb(NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed(); Api.nvttSurfaceToLinearFromSrgb(_ptr, tc);
|
||||
}
|
||||
|
||||
/// <summary>Converts from linear to sRGB (clamped).</summary>
|
||||
public void ToSrgb(NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed(); Api.nvttSurfaceToSrgb(_ptr, tc);
|
||||
}
|
||||
|
||||
/// <summary>Converts from sRGB to linear (unclamped).</summary>
|
||||
public void ToLinearFromSrgbUnclamped(NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed(); Api.nvttSurfaceToLinearFromSrgbUnclamped(_ptr, tc);
|
||||
}
|
||||
|
||||
/// <summary>Converts from linear to sRGB (unclamped).</summary>
|
||||
public void ToSrgbUnclamped(NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed(); Api.nvttSurfaceToSrgbUnclamped(_ptr, tc);
|
||||
}
|
||||
|
||||
/// <summary>Applies gamma expansion (toLinear) to all channels.</summary>
|
||||
public void ToLinear(float gamma, NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed(); Api.nvttSurfaceToLinear(_ptr, gamma, tc);
|
||||
}
|
||||
|
||||
/// <summary>Applies gamma compression to all channels.</summary>
|
||||
public void ToGamma(float gamma, NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed(); Api.nvttSurfaceToGamma(_ptr, gamma, tc);
|
||||
}
|
||||
|
||||
/// <summary>Applies gamma expansion to a single channel.</summary>
|
||||
public void ToLinearChannel(int channel, float gamma, NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed(); Api.nvttSurfaceToLinearChannel(_ptr, channel, gamma, tc);
|
||||
}
|
||||
|
||||
/// <summary>Applies gamma compression to a single channel.</summary>
|
||||
public void ToGammaChannel(int channel, float gamma, NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed(); Api.nvttSurfaceToGammaChannel(_ptr, channel, gamma, tc);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Pixel operations
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Applies a 4×4 colour transform matrix plus per-channel offset.</summary>
|
||||
public void Transform(
|
||||
float[] w0, float[] w1, float[] w2, float[] w3, float[] offset,
|
||||
NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
fixed (float* pw0 = w0, pw1 = w1, pw2 = w2, pw3 = w3, po = offset)
|
||||
{
|
||||
Api.nvttSurfaceTransform(_ptr, pw0, pw1, pw2, pw3, po, tc);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Rearranges channels: result[0]=src[r], result[1]=src[g], etc.</summary>
|
||||
public void Swizzle(int r, int g, int b, int a, NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed(); Api.nvttSurfaceSwizzle(_ptr, r, g, b, a, tc);
|
||||
}
|
||||
|
||||
/// <summary>Applies <c>x = x * scale + bias</c> to a single channel.</summary>
|
||||
public void ScaleBias(int channel, float scale, float bias,
|
||||
NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed(); Api.nvttSurfaceScaleBias(_ptr, channel, scale, bias, tc);
|
||||
}
|
||||
|
||||
/// <summary>Clamps a channel to [<paramref name="low"/>, <paramref name="high"/>].</summary>
|
||||
public void Clamp(int channel, float low, float high, NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed(); Api.nvttSurfaceClamp(_ptr, channel, low, high, tc);
|
||||
}
|
||||
|
||||
/// <summary>Blends toward a constant RGBA colour by factor <paramref name="t"/>.</summary>
|
||||
public void Blend(float r, float g, float b, float a, float t,
|
||||
NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed(); Api.nvttSurfaceBlend(_ptr, r, g, b, a, t, tc);
|
||||
}
|
||||
|
||||
/// <summary>Multiplies RGB by alpha.</summary>
|
||||
public void PremultiplyAlpha(NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed(); Api.nvttSurfacePremultiplyAlpha(_ptr, tc);
|
||||
}
|
||||
|
||||
/// <summary>Divides RGB by alpha (with epsilon guard against divide-by-zero).</summary>
|
||||
public void DemultiplyAlpha(float epsilon = 1e-6f, NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed(); Api.nvttSurfaceDemultiplyAlpha(_ptr, epsilon, tc);
|
||||
}
|
||||
|
||||
/// <summary>Converts to greyscale by weighted sum of channels.</summary>
|
||||
public void ToGreyScale(float redScale, float greenScale, float blueScale,
|
||||
float alphaScale, NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Api.nvttSurfaceToGreyScale(_ptr, redScale, greenScale, blueScale, alphaScale, tc);
|
||||
}
|
||||
|
||||
/// <summary>Fills the edge border of the surface with the given colour.</summary>
|
||||
public void SetBorder(float r, float g, float b, float a,
|
||||
NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed(); Api.nvttSurfaceSetBorder(_ptr, r, g, b, a, tc);
|
||||
}
|
||||
|
||||
/// <summary>Fills the entire surface with a constant colour.</summary>
|
||||
public void Fill(float r, float g, float b, float a, NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed(); Api.nvttSurfaceFill(_ptr, r, g, b, a, tc);
|
||||
}
|
||||
|
||||
/// <summary>Scales alpha so that alpha-test coverage matches the given target.</summary>
|
||||
public void ScaleAlphaToCoverage(float coverage, float alphaRef = 0.5f,
|
||||
int alphaChannel = 3, NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Api.nvttSurfaceScaleAlphaToCoverage(_ptr, coverage, alphaRef, alphaChannel, tc);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// HDR encodings
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Encodes to RGBM (RGB * M, M in alpha).</summary>
|
||||
public void ToRGBM(float range = 6f, float threshold = 0.25f,
|
||||
NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed(); Api.nvttSurfaceToRGBM(_ptr, range, threshold, tc);
|
||||
}
|
||||
|
||||
/// <summary>Decodes from RGBM back to linear HDR.</summary>
|
||||
public void FromRGBM(float range = 6f, float threshold = 0.25f,
|
||||
NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed(); Api.nvttSurfaceFromRGBM(_ptr, range, threshold, tc);
|
||||
}
|
||||
|
||||
/// <summary>Encodes to RGBE (Radiance HDR format).</summary>
|
||||
public void ToRGBE(int mantissaBits = 9, int exponentBits = 5,
|
||||
NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed(); Api.nvttSurfaceToRGBE(_ptr, mantissaBits, exponentBits, tc);
|
||||
}
|
||||
|
||||
/// <summary>Decodes from RGBE back to linear HDR.</summary>
|
||||
public void FromRGBE(int mantissaBits = 9, int exponentBits = 5,
|
||||
NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed(); Api.nvttSurfaceFromRGBE(_ptr, mantissaBits, exponentBits, tc);
|
||||
}
|
||||
|
||||
/// <summary>Converts to YCoCg colour space.</summary>
|
||||
public void ToYCoCg(NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed(); Api.nvttSurfaceToYCoCg(_ptr, tc);
|
||||
}
|
||||
|
||||
/// <summary>Converts from YCoCg back to RGB.</summary>
|
||||
public void FromYCoCg(NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed(); Api.nvttSurfaceFromYCoCg(_ptr, tc);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Normal-map operations
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Generates a normal map from the surface (treated as a height map)
|
||||
/// using four blur kernel sizes.
|
||||
/// </summary>
|
||||
public void ToNormalMap(float sm, float medium, float big, float large,
|
||||
NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Api.nvttSurfaceToNormalMap(_ptr, sm, medium, big, large, tc);
|
||||
}
|
||||
|
||||
/// <summary>Re-normalises all normal vectors in the surface.</summary>
|
||||
public void NormalizeNormalMap(NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed(); Api.nvttSurfaceNormalizeNormalMap(_ptr, tc);
|
||||
}
|
||||
|
||||
/// <summary>Applies a normal-space transform.</summary>
|
||||
public void TransformNormals(NvttNormalTransform xform,
|
||||
NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed(); Api.nvttSurfaceTransformNormals(_ptr, xform, tc);
|
||||
}
|
||||
|
||||
/// <summary>Reconstructs normals from a packed representation.</summary>
|
||||
public void ReconstructNormals(NvttNormalTransform xform,
|
||||
NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed(); Api.nvttSurfaceReconstructNormals(_ptr, xform, tc);
|
||||
}
|
||||
|
||||
/// <summary>Packs normals into [0,1] range using <c>n*scale+bias</c>.</summary>
|
||||
public void PackNormals(float scale = 0.5f, float bias = 0.5f,
|
||||
NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed(); Api.nvttSurfacePackNormals(_ptr, scale, bias, tc);
|
||||
}
|
||||
|
||||
/// <summary>Expands packed normals back to [-1,1] range.</summary>
|
||||
public void ExpandNormals(float scale = 2f, float bias = -1f,
|
||||
NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed(); Api.nvttSurfaceExpandNormals(_ptr, scale, bias, tc);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Toksvig specular power map from a normal map.
|
||||
/// Caller owns the returned surface.
|
||||
/// </summary>
|
||||
public NvttSurfaceHandle CreateToksvigMap(float power, NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return new NvttSurfaceHandle(Api.nvttSurfaceCreateToksvigMap(_ptr, power, tc));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Flip
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Flips the surface along the X axis.</summary>
|
||||
public void FlipX(NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed(); Api.nvttSurfaceFlipX(_ptr, tc);
|
||||
}
|
||||
|
||||
/// <summary>Flips the surface along the Y axis.</summary>
|
||||
public void FlipY(NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed(); Api.nvttSurfaceFlipY(_ptr, tc);
|
||||
}
|
||||
|
||||
/// <summary>Flips the surface along the Z axis.</summary>
|
||||
public void FlipZ(NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed(); Api.nvttSurfaceFlipZ(_ptr, tc);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Channel copy / add
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Copies a single channel from another surface.</summary>
|
||||
public bool CopyChannel(NvttSurfaceHandle src, int srcChannel, int dstChannel,
|
||||
NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return NvttInterop.ToBool(
|
||||
Api.nvttSurfaceCopyChannel(_ptr, src.Ptr, srcChannel, dstChannel, tc));
|
||||
}
|
||||
|
||||
/// <summary>Adds a scaled channel from another surface into this one.</summary>
|
||||
public bool AddChannel(NvttSurfaceHandle src, int srcChannel, int dstChannel,
|
||||
float scale = 1f, NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return NvttInterop.ToBool(
|
||||
Api.nvttSurfaceAddChannel(_ptr, src.Ptr, srcChannel, dstChannel, scale, tc));
|
||||
}
|
||||
|
||||
/// <summary>Copies a rectangular region from another surface into this one.</summary>
|
||||
public bool Copy(NvttSurfaceHandle src,
|
||||
int xsrc, int ysrc, int zsrc,
|
||||
int xsize, int ysize, int zsize,
|
||||
int xdst, int ydst, int zdst,
|
||||
NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return NvttInterop.ToBool(
|
||||
Api.nvttSurfaceCopy(_ptr, src.Ptr,
|
||||
xsrc, ysrc, zsrc, xsize, ysize, zsize, xdst, ydst, zdst, tc));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// GPU transfer
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Uploads the surface to the GPU (CUDA). <paramref name="performCopy"/> clones instead of moving.</summary>
|
||||
public void ToGPU(bool performCopy = false, NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Api.nvttSurfaceToGPU(_ptr, NvttInterop.ToNvtt(performCopy), tc);
|
||||
}
|
||||
|
||||
/// <summary>Downloads the surface back to CPU memory.</summary>
|
||||
public void ToCPU(NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed(); Api.nvttSurfaceToCPU(_ptr, tc);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Quantize / binarize
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Quantizes a channel to the given bit depth.</summary>
|
||||
public void Quantize(int channel, int bits,
|
||||
bool exactEndPoints = false, bool dither = false,
|
||||
NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Api.nvttSurfaceQuantize(_ptr, channel, bits,
|
||||
NvttInterop.ToNvtt(exactEndPoints), NvttInterop.ToNvtt(dither), tc);
|
||||
}
|
||||
|
||||
/// <summary>Binarizes a channel using a threshold.</summary>
|
||||
public void Binarize(int channel, float threshold, bool dither = false,
|
||||
NvttTimingContext* tc = null)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Api.nvttSurfaceBinarize(_ptr, channel, threshold,
|
||||
NvttInterop.ToNvtt(dither), tc);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_ptr == null)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(NvttSurfaceHandle));
|
||||
}
|
||||
}
|
||||
}
|
||||
144
src/ThridParty/Ghost.Nvtt/Wrapper/NvttSurfaceSet.cs
Normal file
144
src/ThridParty/Ghost.Nvtt/Wrapper/NvttSurfaceSet.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
namespace Ghost.Nvtt.Wrapper;
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper around an nvtt surface set — a collection of faces and mip levels
|
||||
/// loaded from a DDS file or built programmatically.
|
||||
/// </summary>
|
||||
public sealed unsafe class NvttSurfaceSetHandle : IDisposable
|
||||
{
|
||||
private NvttSurfaceSet* _ptr;
|
||||
|
||||
/// <summary>Raw pointer - use only when calling the native API directly.</summary>
|
||||
public NvttSurfaceSet* Ptr => _ptr;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Construction / destruction
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public NvttSurfaceSetHandle() => _ptr = Api.nvttCreateSurfaceSet();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_ptr != null)
|
||||
{
|
||||
Api.nvttDestroySurfaceSet(_ptr);
|
||||
_ptr = null;
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Read-only properties
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Texture dimensionality stored in this set.</summary>
|
||||
public NvttTextureType TextureType
|
||||
{
|
||||
get { ThrowIfDisposed(); return Api.nvttSurfaceSetGetTextureType(_ptr); }
|
||||
}
|
||||
|
||||
/// <summary>Number of faces (1 for 2-D / 3-D, 6 for cube maps).</summary>
|
||||
public int FaceCount
|
||||
{
|
||||
get { ThrowIfDisposed(); return Api.nvttSurfaceSetGetFaceCount(_ptr); }
|
||||
}
|
||||
|
||||
/// <summary>Number of mip levels.</summary>
|
||||
public int MipmapCount
|
||||
{
|
||||
get { ThrowIfDisposed(); return Api.nvttSurfaceSetGetMipmapCount(_ptr); }
|
||||
}
|
||||
|
||||
/// <summary>Width of the base (mip 0) image in pixels.</summary>
|
||||
public int Width
|
||||
{
|
||||
get { ThrowIfDisposed(); return Api.nvttSurfaceSetGetWidth(_ptr); }
|
||||
}
|
||||
|
||||
/// <summary>Height of the base (mip 0) image in pixels.</summary>
|
||||
public int Height
|
||||
{
|
||||
get { ThrowIfDisposed(); return Api.nvttSurfaceSetGetHeight(_ptr); }
|
||||
}
|
||||
|
||||
/// <summary>Depth of the base (mip 0) image (1 for 2-D textures).</summary>
|
||||
public int Depth
|
||||
{
|
||||
get { ThrowIfDisposed(); return Api.nvttSurfaceSetGetDepth(_ptr); }
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Surface access
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Returns the raw <see cref="NvttSurface"/> pointer for the given face
|
||||
/// and mip level. The pointer is owned by this surface set - do NOT dispose
|
||||
/// it.
|
||||
/// </summary>
|
||||
public NvttSurface* GetSurfacePtr(int faceId, int mipId, bool expectSigned = false)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return Api.nvttSurfaceSetGetSurface(_ptr, faceId, mipId,
|
||||
NvttInterop.ToNvtt(expectSigned));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Load / Save
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Resets the surface set to an empty state.</summary>
|
||||
public void Reset()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Api.nvttResetSurfaceSet(_ptr);
|
||||
}
|
||||
|
||||
/// <summary>Loads from a DDS file. Returns <c>false</c> on failure.</summary>
|
||||
public bool LoadDDS(string fileName, bool forceNormal = false)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Span<byte> buf = stackalloc byte[NvttInterop._MAX_STACK_PATH];
|
||||
var utf8 = NvttInterop.ToUtf8(fileName, buf);
|
||||
fixed (byte* p = utf8)
|
||||
{
|
||||
return NvttInterop.ToBool(
|
||||
Api.nvttSurfaceSetLoadDDS(_ptr, (sbyte*)p,
|
||||
NvttInterop.ToNvtt(forceNormal)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Loads from a managed byte array containing DDS data. Returns <c>false</c> on failure.</summary>
|
||||
public bool LoadDDSFromMemory(ReadOnlySpan<byte> data, bool forceNormal = false)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
fixed (byte* p = data)
|
||||
{
|
||||
return NvttInterop.ToBool(
|
||||
Api.nvttSurfaceSetLoadDDSFromMemory(_ptr, p, (ulong)data.Length,
|
||||
NvttInterop.ToNvtt(forceNormal)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Saves a single face/mip as an image file. Returns <c>false</c> on failure.</summary>
|
||||
public bool SaveImage(string fileName, int faceId, int mipId)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Span<byte> buf = stackalloc byte[NvttInterop._MAX_STACK_PATH];
|
||||
var utf8 = NvttInterop.ToUtf8(fileName, buf);
|
||||
fixed (byte* p = utf8)
|
||||
{
|
||||
return NvttInterop.ToBool(
|
||||
Api.nvttSurfaceSetSaveImage(_ptr, (sbyte*)p, faceId, mipId));
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_ptr == null)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(NvttSurfaceSetHandle));
|
||||
}
|
||||
}
|
||||
}
|
||||
99
src/ThridParty/Ghost.Nvtt/Wrapper/NvttTimingContext.cs
Normal file
99
src/ThridParty/Ghost.Nvtt/Wrapper/NvttTimingContext.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
namespace Ghost.Nvtt.Wrapper;
|
||||
|
||||
/// <summary>
|
||||
/// Wraps an nvtt timing context that records per-operation wall-clock times.
|
||||
/// Obtain one from <see cref="NvttContext.TimingContext"/> or create a
|
||||
/// standalone instance and pass it as the optional <c>tc</c> parameter on
|
||||
/// surface methods.
|
||||
/// </summary>
|
||||
public sealed unsafe class NvttTimingContextHandle : IDisposable
|
||||
{
|
||||
private NvttTimingContext* _ptr;
|
||||
|
||||
/// <summary>Raw pointer - use only when calling the native API directly.</summary>
|
||||
public NvttTimingContext* Ptr => _ptr;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Construction / destruction
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Creates a timing context at the specified detail level (0 = off, higher = more detail).</summary>
|
||||
public NvttTimingContextHandle(int detailLevel = 1)
|
||||
=> _ptr = Api.nvttCreateTimingContext(detailLevel);
|
||||
|
||||
/// <summary>Wraps an already-owned native pointer (ownership transferred to this object).</summary>
|
||||
internal NvttTimingContextHandle(NvttTimingContext* owned) => _ptr = owned;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_ptr != null)
|
||||
{
|
||||
Api.nvttDestroyTimingContext(_ptr);
|
||||
_ptr = null;
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Properties
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Sets the detail level (0 = disabled; higher values record more sub-operations).
|
||||
/// </summary>
|
||||
public int DetailLevel
|
||||
{
|
||||
set
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Api.nvttTimingContextSetDetailLevel(_ptr, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Number of timing records captured so far.</summary>
|
||||
public int RecordCount
|
||||
{
|
||||
get
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return Api.nvttTimingContextGetRecordCount(_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Returns the description and elapsed seconds for the record at
|
||||
/// <paramref name="index"/>.
|
||||
/// </summary>
|
||||
public (string description, double seconds) GetRecord(int index)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Span<byte> buf = stackalloc byte[256];
|
||||
double seconds;
|
||||
fixed (byte* p = buf)
|
||||
{
|
||||
Api.nvttTimingContextGetRecordSafe(_ptr, index, (sbyte*)p, (nuint)buf.Length, &seconds);
|
||||
var desc = NvttInterop.FromUtf8((sbyte*)p) ?? string.Empty;
|
||||
return (desc, seconds);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Prints all timing records to stdout via the native library.</summary>
|
||||
public void PrintRecords()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Api.nvttTimingContextPrintRecords(_ptr);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_ptr == null)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(NvttTimingContextHandle));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user