new image loader API

pull/48/head
cosmonaut 2023-04-19 00:41:18 -07:00
parent e3c2f0e119
commit 0ea60a376b
2 changed files with 145 additions and 181 deletions

@ -1 +1 @@
Subproject commit fb5fee5f568bbb9f42920f0fcdd9f85024f5bf00 Subproject commit ebf511133aa6f567c004d687acac474e1649bbde

View File

@ -23,26 +23,18 @@ namespace MoonWorks.Graphics
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyTexture; protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyTexture;
/// <summary> /// <summary>
/// Loads a PNG from a file path. /// Creates a 2D Texture using PNG or QOI data from raw byte data.
/// NOTE: You can queue as many of these as you want on to a command buffer but it MUST be submitted!
/// </summary> /// </summary>
/// <param name="device"></param> public static unsafe Texture FromData(
/// <param name="commandBuffer"></param>
/// <param name="filePath"></param>
/// <returns>A Texture object.</returns>
public static Texture LoadPNG(
GraphicsDevice device, GraphicsDevice device,
CommandBuffer commandBuffer, CommandBuffer commandBuffer,
string filePath Span<byte> data
) { ) {
var pixels = Refresh.Refresh_Image_LoadPNGFromFile( Texture texture;
filePath,
out var width,
out var height,
out var channels
);
var byteCount = (uint) (width * height * channels); fixed (byte *dataPtr = data)
{
var pixels = Refresh.Refresh_Image_Load((nint) dataPtr, data.Length, out var width, out var height, out var len);
TextureCreateInfo textureCreateInfo = new TextureCreateInfo(); TextureCreateInfo textureCreateInfo = new TextureCreateInfo();
textureCreateInfo.Width = (uint) width; textureCreateInfo.Width = (uint) width;
@ -54,168 +46,84 @@ namespace MoonWorks.Graphics
textureCreateInfo.SampleCount = SampleCount.One; textureCreateInfo.SampleCount = SampleCount.One;
textureCreateInfo.UsageFlags = TextureUsageFlags.Sampler; textureCreateInfo.UsageFlags = TextureUsageFlags.Sampler;
var texture = new Texture(device, textureCreateInfo); texture = new Texture(device, textureCreateInfo);
commandBuffer.SetTextureData(texture, pixels, byteCount); commandBuffer.SetTextureData(texture, pixels, (uint) len);
Refresh.Refresh_Image_FreePNG(pixels); Refresh.Refresh_Image_Free(pixels);
}
return texture; return texture;
} }
/// <summary> /// <summary>
/// Loads a PNG from a byte array. /// Creates a 2D Texture using PNG or QOI data from a stream.
/// NOTE: You can queue as many of these as you want on to a command buffer but it MUST be submitted!
/// </summary> /// </summary>
/// <param name="device"></param> public static unsafe Texture FromStream(
/// <param name="commandBuffer"></param>
/// <param name="data"></param>
/// <returns>A Texture object.</returns>
public unsafe static Texture LoadPNG(
GraphicsDevice device, GraphicsDevice device,
CommandBuffer commandBuffer, CommandBuffer commandBuffer,
byte[] data Stream stream
) { ) {
IntPtr pixels; var length = stream.Length;
int width, height, numChannels; var buffer = NativeMemory.Alloc((nuint) length);
var span = new Span<byte>(buffer, (int) length);
stream.ReadExactly(span);
fixed (byte* ptr = &data[0]) var texture = FromData(device, commandBuffer, span);
{
pixels = Refresh.Refresh_Image_LoadPNGFromMemory(
(nint) ptr,
data.Length,
out width,
out height,
out numChannels
);
}
TextureCreateInfo textureCreateInfo = new TextureCreateInfo NativeMemory.Free((void*) buffer);
{
Width = (uint) width,
Height = (uint) height,
Depth = 1,
Format = TextureFormat.R8G8B8A8,
IsCube = false,
LevelCount = 1,
SampleCount = SampleCount.One,
UsageFlags = TextureUsageFlags.Sampler
};
var byteCount = (uint) (width * height * numChannels);
var texture = new Texture(device, textureCreateInfo);
commandBuffer.SetTextureData(texture, pixels, byteCount);
Refresh.Refresh_Image_FreePNG(pixels);
return texture; return texture;
} }
/// <summary> /// <summary>
/// Saves RGBA or BGRA pixel data to a file in PNG format. /// Creates a 2D Texture using PNG or QOI data from a file.
/// </summary> /// </summary>
public unsafe static void SavePNG(string path, int width, int height, TextureFormat format, byte[] pixels) public static Texture FromFile(
{
if (format != TextureFormat.R8G8B8A8 && format != TextureFormat.B8G8R8A8)
{
throw new ArgumentException("Texture format must be RGBA8 or BGRA8!", "format");
}
fixed (byte* ptr = &pixels[0])
{
Refresh.Refresh_Image_SavePNG(path, width, height, Conversions.BoolToByte(format == TextureFormat.B8G8R8A8), (IntPtr) ptr);
}
}
/// <summary>
/// Loads a QOI from a file path.
/// NOTE: You can queue as many of these as you want on to a command buffer but it MUST be submitted!
/// </summary>
/// <param name="device"></param>
/// <param name="commandBuffer"></param>
/// <param name="filePath"></param>
/// <returns>A Texture object.</returns>
public unsafe static Texture LoadQOI(
GraphicsDevice device, GraphicsDevice device,
CommandBuffer commandBuffer, CommandBuffer commandBuffer,
string filePath string path
) { ) {
var pixels = Refresh.Refresh_Image_LoadQOIFromFile( var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read);
filePath, return FromStream(device, commandBuffer, fileStream);
out var width,
out var height,
out var numChannels
);
var byteCount = (uint) (width * height * numChannels);
TextureCreateInfo textureCreateInfo = new TextureCreateInfo
{
Width = (uint) width,
Height = (uint) height,
Depth = 1,
Format = TextureFormat.R8G8B8A8,
IsCube = false,
LevelCount = 1,
SampleCount = SampleCount.One,
UsageFlags = TextureUsageFlags.Sampler
};
var texture = new Texture(device, textureCreateInfo);
commandBuffer.SetTextureData(texture, pixels, byteCount);
Refresh.Refresh_Image_FreeQOI(pixels);
return texture;
} }
/// <summary> /// <summary>
/// Loads a QOI from a byte array. /// Sets data for a texture slice using PNG or QOI data from a stream.
/// NOTE: You can queue as many of these as you want on to a command buffer but it MUST be submitted!
/// </summary> /// </summary>
/// <param name="device"></param> public static unsafe void SetDataFromStream(
/// <param name="commandBuffer"></param>
/// <param name="filePath"></param>
/// <returns>A Texture object.</returns>
public unsafe static Texture LoadQOI(
GraphicsDevice device,
CommandBuffer commandBuffer, CommandBuffer commandBuffer,
byte[] data TextureSlice textureSlice,
Stream stream
) { ) {
IntPtr pixels; var length = stream.Length;
int width, height, numChannels; var buffer = NativeMemory.Alloc((nuint) length);
var span = new Span<byte>(buffer, (int) length);
stream.ReadExactly(span);
fixed (byte* ptr = &data[0]) var pixels = Refresh.Refresh_Image_Load(
{ (nint) buffer,
pixels = Refresh.Refresh_Image_LoadQOIFromMemory( (int) length,
(nint) ptr, out var w,
data.Length, out var h,
out width, out var len
out height,
out numChannels
); );
commandBuffer.SetTextureData(textureSlice, pixels, (uint) len);
Refresh.Refresh_Image_Free(pixels);
NativeMemory.Free((void*) buffer);
} }
TextureCreateInfo textureCreateInfo = new TextureCreateInfo /// <summary>
{ /// Sets data for a texture slice using PNG or QOI data from a file.
Width = (uint) width, /// </summary>
Height = (uint) height, public static void SetDataFromFile(
Depth = 1, CommandBuffer commandBuffer,
Format = TextureFormat.R8G8B8A8, TextureSlice textureSlice,
IsCube = false, string path
LevelCount = 1, ) {
SampleCount = SampleCount.One, var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read);
UsageFlags = TextureUsageFlags.Sampler SetDataFromStream(commandBuffer, textureSlice, fileStream);
};
var byteCount = (uint) (width * height * numChannels);
var texture = new Texture(device, textureCreateInfo);
commandBuffer.SetTextureData(texture, pixels, byteCount);
Refresh.Refresh_Image_FreePNG(pixels);
return texture;
} }
public unsafe static Texture LoadDDS(GraphicsDevice graphicsDevice, CommandBuffer commandBuffer, System.IO.Stream stream) public unsafe static Texture LoadDDS(GraphicsDevice graphicsDevice, CommandBuffer commandBuffer, System.IO.Stream stream)
@ -669,5 +577,61 @@ namespace MoonWorks.Graphics
); );
} }
} }
/// <summary>
/// Asynchronously saves RGBA or BGRA pixel data to a file in PNG format.
/// Warning: this is expensive!
/// </summary>
public unsafe void SavePNG(string path)
{
#if DEBUG
if (Format != TextureFormat.R8G8B8A8 && Format != TextureFormat.B8G8R8A8)
{
throw new ArgumentException("Texture format must be RGBA or BGRA!", "format");
}
#endif
var buffer = new Buffer(Device, 0, Width * Height * 4); // this creates garbage... oh well
// immediately request the data copy
var commandBuffer = Device.AcquireCommandBuffer();
commandBuffer.CopyTextureToBuffer(this, buffer);
Device.Submit(commandBuffer);
var byteCount = buffer.Size;
var pixelsPtr = NativeMemory.Alloc((nuint) byteCount);
var pixelsSpan = new Span<byte>(pixelsPtr, (int) byteCount);
Device.Wait(); // make sure the data transfer is done...
buffer.GetData(pixelsSpan);
if (Format == TextureFormat.B8G8R8A8)
{
var rgbaPtr = NativeMemory.Alloc((nuint) byteCount);
var rgbaSpan = new Span<byte>(rgbaPtr, (int) byteCount);
for (var i = 0; i < byteCount; i += 4)
{
rgbaSpan[i] = pixelsSpan[i + 2];
rgbaSpan[i + 1] = pixelsSpan[i + 1];
rgbaSpan[i + 2] = pixelsSpan[i];
rgbaSpan[i + 3] = pixelsSpan[i + 3];
}
Refresh.Refresh_Image_SavePNG(path, (nint) rgbaPtr, (int) Width, (int) Height);
NativeMemory.Free((void*) rgbaPtr);
}
else
{
fixed (byte* ptr = pixelsSpan)
{
Refresh.Refresh_Image_SavePNG(path, (nint) ptr, (int) Width, (int) Height);
}
}
NativeMemory.Free(pixelsPtr);
}
} }
} }