/// <summary> /// Initializes a new instance of the <see cref="GorgonBufferReadWriteView"/> class. /// </summary> /// <param name="buffer">The buffer to assign to the view.</param> /// <param name="format">The format of the view.</param> /// <param name="formatInfo">Information about the format.</param> /// <param name="elementStart">The first element in the buffer to view.</param> /// <param name="elementCount">The number of elements in the view.</param> /// <param name="totalElementCount">The total number of elements in the buffer.</param> /// <exception cref="ArgumentNullException">Thrown when the <paramref name="buffer"/>, or the <paramref name="formatInfo"/> parameter is <b>null</b>.</exception> internal GorgonBufferReadWriteView(GorgonBuffer buffer, BufferFormat format, GorgonFormatInfo formatInfo, int elementStart, int elementCount, int totalElementCount) : base(buffer, elementStart, elementCount, totalElementCount) { Buffer = buffer; FormatInformation = formatInfo ?? throw new ArgumentNullException(nameof(formatInfo)); Format = format; }
/// <summary>Function to load the image file into memory.</summary> /// <param name="file">The stream for the file to load.</param> /// <param name="name">The name of the file.</param> /// <returns>The image data, the virtual file entry for the working file and the original pixel format of the file.</returns> public (IGorgonImage image, IGorgonVirtualFile workingFile, BufferFormat originalFormat) LoadImageFile(Stream file, string name) { IGorgonImage result = null; IGorgonVirtualFile workFile; BufferFormat originalFormat; IGorgonImageInfo imageInfo = DefaultCodec.GetMetaData(file); originalFormat = imageInfo.Format; var formatInfo = new GorgonFormatInfo(imageInfo.Format); // We absolutely need to have an extension, or else the texconv tool will not work. if ((DefaultCodec.CodecCommonExtensions.Count > 0) && (!string.Equals(Path.GetExtension(name), DefaultCodec.CodecCommonExtensions[0], System.StringComparison.OrdinalIgnoreCase))) { _log.Print("Adding DDS extension to working file or else external tools may not be able to read it.", LoggingLevel.Verbose); name = Path.ChangeExtension(name, DefaultCodec.CodecCommonExtensions[0]); } _log.Print($"Copying content file {name} to {ScratchArea.FileSystem.MountPoints.First().PhysicalPath} as working file...", LoggingLevel.Intermediate); // Copy to a working file. using (Stream outStream = ScratchArea.OpenStream(name, FileMode.Create)) { file.CopyTo(outStream); workFile = ScratchArea.FileSystem.GetFile(name); } _log.Print($"{workFile.FullPath} is now the working file for the image editor.", LoggingLevel.Intermediate); if (formatInfo.IsCompressed) { _log.Print($"Image is compressed using [{formatInfo.Format}] as its pixel format.", LoggingLevel.Intermediate); if (_compressor == null) { throw new GorgonException(GorgonResult.CannotRead, string.Format(Resources.GORIMG_ERR_COMPRESSED_FILE, formatInfo.Format)); } _log.Print($"Loading image '{workFile.FullPath}'...", LoggingLevel.Simple); result = _compressor.Decompress(ref workFile, imageInfo); if (result == null) { throw new GorgonException(GorgonResult.CannotRead, string.Format(Resources.GORIMG_ERR_COMPRESSED_FILE, formatInfo.Format)); } _log.Print($"Loaded compressed ([{formatInfo.Format}]) image data as [{result.Format}]", LoggingLevel.Intermediate); } else { _log.Print($"Loading image '{workFile.FullPath}'...", LoggingLevel.Simple); using (Stream workingFileStream = workFile.OpenStream()) { result = DefaultCodec.LoadFromStream(workingFileStream); } } return(result, workFile, originalFormat); }
/// <summary> /// Initializes a new instance of the <see cref="GorgonBufferView"/> class. /// </summary> /// <param name="buffer">The buffer to bind to the view.</param> /// <param name="format">The format of the view into the buffer.</param> /// <param name="formatInfo">The information about the format being used.</param> /// <param name="startingElement">The starting element in the buffer to view.</param> /// <param name="elementCount">The number of elements in the buffer to view.</param> /// <param name="totalElementCount">The total number of elements in the buffer.</param> /// <exception cref="ArgumentNullException">Thrown when the <paramref name="buffer"/>, or the <paramref name="formatInfo"/> parameter is <b>null</b>.</exception> internal GorgonBufferView(GorgonBuffer buffer, BufferFormat format, GorgonFormatInfo formatInfo, int startingElement, int elementCount, int totalElementCount) : base(buffer, startingElement, elementCount, totalElementCount) { FormatInformation = formatInfo ?? throw new ArgumentNullException(nameof(formatInfo)); Format = format; }
/// <summary> /// Initializes a new instance of the <see cref="GorgonTexture3DView"/> class. /// </summary> /// <param name="texture">The <see cref="GorgonTexture3D"/> being viewed.</param> /// <param name="format">The format for the view.</param> /// <param name="formatInfo">The information about the view format.</param> /// <param name="firstMipLevel">The first mip level to view.</param> /// <param name="mipCount">The number of mip levels to view.</param> /// <exception cref="ArgumentNullException">Thrown when the <paramref name="texture"/>, or the <paramref name="formatInfo"/> parameter is <b>null</b>.</exception> internal GorgonTexture3DView(GorgonTexture3D texture, BufferFormat format, GorgonFormatInfo formatInfo, int firstMipLevel, int mipCount) : base(texture) { FormatInformation = formatInfo ?? throw new ArgumentNullException(nameof(formatInfo)); Format = format; Texture = texture; MipSlice = firstMipLevel; MipCount = mipCount; }
/// <summary> /// Function to create a new <see cref="GorgonVertexBufferReadWriteView"/> for this buffer. /// </summary> /// <param name="format">The format for the view.</param> /// <param name="startElement">[Optional] The first element to start viewing from.</param> /// <param name="elementCount">[Optional] The number of elements to view.</param> /// <returns>A <see cref="GorgonVertexBufferReadWriteView"/> used to bind the buffer to a shader.</returns> /// <exception cref="GorgonException">Thrown when this buffer does not have a <see cref="Binding"/> of <see cref="VertexIndexBufferBinding.UnorderedAccess"/>. /// <para>-or-</para> /// <para>Thrown when this buffer has a usage of <see cref="ResourceUsage.Staging"/>.</para> /// </exception> /// <exception cref="ArgumentException">Thrown when the <paramref name="format"/> is typeless or is not a supported format for unordered access views.</exception> /// <remarks> /// <para> /// This will create an unordered access view that makes a buffer accessible to shaders using unordered access to the data. This allows viewing of the buffer data in a /// different format, or even a subsection of the buffer from within the shader. /// </para> /// <para> /// The <paramref name="format"/> parameter is used present the buffer data as another format type to the shader. /// </para> /// <para> /// The <paramref name="startElement"/> parameter defines the starting data element to allow access to within the shader. If this value falls outside of the range of available elements, then it /// will be clipped to the upper and lower bounds of the element range. If this value is left at 0, then first element is viewed. /// </para> /// <para> /// The <paramref name="elementCount"/> parameter defines how many elements to allow access to inside of the view. If this value falls outside of the range of available elements, then it will be /// clipped to the upper or lower bounds of the element range. If this value is left at 0, then the entire buffer is viewed. /// </para> /// </remarks> public GorgonVertexBufferReadWriteView GetReadWriteView(BufferFormat format, int startElement = 0, int elementCount = 0) { if ((Usage == ResourceUsage.Staging) || ((Binding & VertexIndexBufferBinding.UnorderedAccess) != VertexIndexBufferBinding.UnorderedAccess)) { throw new GorgonException(GorgonResult.CannotCreate, string.Format(Resources.GORGFX_ERR_UAV_RESOURCE_NOT_VALID, Name)); } if (!Graphics.FormatSupport.TryGetValue(format, out IGorgonFormatSupportInfo support)) { throw new GorgonException(GorgonResult.CannotCreate, string.Format(Resources.GORGFX_ERR_UAV_FORMAT_INVALID, format)); } if ((support.FormatSupport & BufferFormatSupport.TypedUnorderedAccessView) != BufferFormatSupport.TypedUnorderedAccessView) { throw new ArgumentException(string.Format(Resources.GORGFX_ERR_UAV_FORMAT_INVALID, format), nameof(format)); } // Ensure the size of the data type fits the requested format. var info = new GorgonFormatInfo(format); if (info.IsTypeless) { throw new ArgumentException(Resources.GORGFX_ERR_VIEW_NO_TYPELESS, nameof(format)); } int totalElementCount = GetTotalElementCount(info); startElement = startElement.Min(totalElementCount - 1).Max(0); if (elementCount <= 0) { elementCount = totalElementCount - startElement; } elementCount = elementCount.Min(totalElementCount - startElement).Max(1); var key = new BufferShaderViewKey(startElement, elementCount, format); GorgonVertexBufferReadWriteView result = GetReadWriteView <GorgonVertexBufferReadWriteView>(key); if (result != null) { return(result); } result = new GorgonVertexBufferReadWriteView(this, format, info, startElement, elementCount, totalElementCount); result.CreateNativeView(); RegisterReadWriteView(key, result); return(result); }
/// <summary> /// Function to convert the pixel format of an image into another pixel format. /// </summary> /// <param name="baseImage">The image to convert.</param> /// <param name="format">The new pixel format for the image.</param> /// <param name="dithering">[Optional] Flag to indicate the type of dithering to perform when the bit depth for the <paramref name="format"/> is lower than the original bit depth.</param> /// <returns>A <see cref="IGorgonImage"/> containing the image data with the converted pixel format.</returns> /// <exception cref="ArgumentNullException">Thrown when the <paramref name="baseImage"/> is <b>null</b>.</exception> /// <exception cref="ArgumentEmptyException">Thrown when the <paramref name="format"/> is set to <see cref="BufferFormat.Unknown"/>. /// <para>-or-</para> /// <para>Thrown when the original format could not be converted into the desired <paramref name="format"/>.</para> /// </exception> /// <remarks> /// <para> /// Use this to convert an image format from one to another. The conversion functionality uses Windows Imaging Components (WIC) to perform the conversion. /// </para> /// <para> /// Because this method uses WIC, not all formats will be convertible. To determine if a format can be converted, use the <see cref="GorgonImage.CanConvertToFormat"/> method. /// </para> /// <para> /// For the <see cref="BufferFormat.B4G4R4A4_UNorm"/> format, Gorgon has to perform a manual conversion since that format is not supported by WIC. Because of this, the /// <paramref name="dithering"/> flag will be ignored when downsampling to that format. /// </para> /// </remarks> public static IGorgonImage ConvertToFormat(this IGorgonImage baseImage, BufferFormat format, ImageDithering dithering = ImageDithering.None) { if (baseImage == null) { throw new ArgumentNullException(nameof(baseImage)); } if ((format == BufferFormat.Unknown) || (!baseImage.CanConvertToFormat(format))) { throw new ArgumentException(string.Format(Resources.GORIMG_ERR_FORMAT_NOT_SUPPORTED, format), nameof(format)); } if (format == baseImage.Format) { return(baseImage); } // If we've asked for 4 bit per channel BGRA, then we have to convert the base image to B8R8G8A8,and then convert manually (no support in WIC). if (format == BufferFormat.B4G4R4A4_UNorm) { return(ConvertToB4G4R4A4(baseImage, dithering)); } // If we're currently using B4G4R4A4, then manually convert (no support in WIC). if (baseImage.Format == BufferFormat.B4G4R4A4_UNorm) { return(ConvertFromB4G4R4A4(baseImage, format)); } var destInfo = new GorgonFormatInfo(format); WicUtilities wic = null; IGorgonImage newImage = null; try { wic = new WicUtilities(); newImage = wic.ConvertToFormat(baseImage, format, dithering, baseImage.FormatInfo.IsSRgb, destInfo.IsSRgb); newImage.CopyTo(baseImage); return(baseImage); } finally { newImage?.Dispose(); wic?.Dispose(); } }
/// <summary> /// Function to create or retrieve a <see cref="GorgonBufferView"/> for this buffer. /// </summary> /// <param name="format">The format of the view</param> /// <param name="startElement">[Optional] The starting element to begin viewing at.</param> /// <param name="elementCount">[Optional] The number of elements to view.</param> /// <returns>The <see cref="GorgonBufferView"/> requested for this buffer.</returns> /// <exception cref="GorgonException">Thrown if this buffer is a staging resource, or does not have a binding flag for shader access.</exception> /// <exception cref="ArgumentException">Thrown if the <paramref name="format"/> is typeless.</exception> /// <remarks> /// <para> /// This will create a shader resource view that makes a buffer accessible to shaders. This allows viewing of the buffer data in a different format, or even a subsection of the buffer from within /// the shader. /// </para> /// <para> /// The <paramref name="format"/> parameter is used present the buffer data as a specific <see cref="BufferFormat"/> type to the shader. /// </para> /// <para> /// The <paramref name="startElement"/> parameter defines the starting data element to allow access to within the shader. If this value falls outside of the range of available elements, then it /// will be clipped to the upper and lower bounds of the element range. If this value is left at 0, then first element is viewed. /// </para> /// <para> /// The <paramref name="elementCount"/> parameter defines how many elements to allow access to inside of the view. If this value falls outside of the range of available elements, then it will be /// clipped to the upper or lower bounds of the element range. If this value is left at 0, then the entire buffer is viewed. /// </para> /// </remarks> /// <seealso cref="BufferFormat"/> public GorgonBufferView GetShaderResourceView(BufferFormat format, int startElement = 0, int elementCount = 0) { if (format == BufferFormat.Unknown) { throw new ArgumentException(Resources.GORGFX_ERR_VIEW_UNKNOWN_FORMAT, nameof(format)); } if (Usage == ResourceUsage.Staging) { throw new GorgonException(GorgonResult.CannotCreate, Resources.GORGFX_ERR_BUFFER_STAGING_CANNOT_BE_BOUND_TO_GPU); } if ((Binding & BufferBinding.Shader) != BufferBinding.Shader) { throw new GorgonException(GorgonResult.CannotCreate, string.Format(Resources.GORGFX_ERR_BUFFER_INCORRECT_BINDING, BufferBinding.Shader)); } var formatInfo = new GorgonFormatInfo(format); if (formatInfo.IsTypeless) { throw new ArgumentException(string.Format(Resources.GORGFX_ERR_VIEW_NO_TYPELESS)); } int totalElementCount = GetTotalElementCount(formatInfo); startElement = startElement.Min(totalElementCount - 1).Max(0); // If we didn't specify a count, then do so now. if (elementCount < 1) { elementCount = totalElementCount - startElement; } elementCount = elementCount.Min(totalElementCount - startElement).Max(1); var key = new BufferShaderViewKey(startElement, elementCount, format); if (GetView(key) is GorgonBufferView view) { return(view); } view = new GorgonBufferView(this, format, formatInfo, startElement, elementCount, totalElementCount); view.CreateNativeView(); RegisterView(key, view); return(view); }
/// <summary> /// Initializes a new instance of the <see cref="GorgonTexture1DReadWriteView"/> class. /// </summary> /// <param name="texture">The texture to view.</param> /// <param name="format">The format for the view.</param> /// <param name="formatInfo">Information about the format.</param> /// <param name="firstMipLevel">The first mip level to view.</param> /// <param name="arrayIndex">The first array index to view.</param> /// <param name="arrayCount">The number of array indices to view.</param> /// <exception cref="ArgumentNullException">Thrown when the <paramref name="texture"/>, or the <paramref name="formatInfo"/> parameter is <b>null</b>.</exception> internal GorgonTexture1DReadWriteView(GorgonTexture1D texture, BufferFormat format, GorgonFormatInfo formatInfo, int firstMipLevel, int arrayIndex, int arrayCount) : base(texture) { FormatInformation = formatInfo ?? throw new ArgumentNullException(nameof(formatInfo)); Format = format; Texture = texture; Bounds = new GorgonRange(0, Width); MipSlice = firstMipLevel; ArrayIndex = arrayIndex; ArrayCount = arrayCount; }
/// <summary> /// Initializes a new instance of the <see cref="GorgonTexture3DReadWriteView"/> class. /// </summary> /// <param name="texture">The texture to view.</param> /// <param name="format">The format for the view.</param> /// <param name="formatInfo">Information about the format.</param> /// <param name="firstMipLevel">The first mip level to view.</param> /// <param name="startDepthSlice">The first depth slice to view.</param> /// <param name="depthSliceCount">The number of depth slices to view.</param> /// <exception cref="ArgumentNullException">Thrown when the <paramref name="texture"/>, or the <paramref name="formatInfo"/> parameter is <b>null</b>.</exception> internal GorgonTexture3DReadWriteView(GorgonTexture3D texture, BufferFormat format, GorgonFormatInfo formatInfo, int firstMipLevel, int startDepthSlice, int depthSliceCount) : base(texture) { FormatInformation = formatInfo ?? throw new ArgumentNullException(nameof(formatInfo)); Format = format; Texture = texture; Bounds = new DX.Rectangle(0, 0, Width, Height); MipSlice = firstMipLevel; StartDepthSlice = startDepthSlice; DepthSliceCount = depthSliceCount; }
/// <summary> /// Initializes a new instance of the <see cref="GorgonTexture2DView"/> class. /// </summary> /// <param name="texture">The <see cref="GorgonTexture2D"/> being viewed.</param> /// <param name="format">The format for the view.</param> /// <param name="formatInfo">The information about the view format.</param> /// <param name="firstMipLevel">The first mip level to view.</param> /// <param name="mipCount">The number of mip levels to view.</param> /// <param name="arrayIndex">The first array index to view.</param> /// <param name="arrayCount">The number of array indices to view.</param> /// <exception cref="ArgumentNullException">Thrown when the <paramref name="texture"/>, or the <paramref name="formatInfo"/> parameter is <b>null</b>.</exception> internal GorgonTexture2DView(GorgonTexture2D texture, BufferFormat format, GorgonFormatInfo formatInfo, int firstMipLevel, int mipCount, int arrayIndex, int arrayCount) : base(texture) { FormatInformation = formatInfo ?? throw new ArgumentNullException(nameof(formatInfo)); Format = format; Texture = texture; Bounds = new DX.Rectangle(0, 0, Width, Height); MipSlice = firstMipLevel; MipCount = mipCount; ArrayIndex = arrayIndex; ArrayCount = arrayCount; }
/// <summary> /// Function to convert this input layout into a <see cref="GorgonStreamOutLayout"/> /// </summary> /// <param name="stream">[Optional] The output stream to use.</param> /// <param name="slot">[Optional] The associated stream output buffer that is bound to the pipeline.</param> /// <returns>A new <see cref="GorgonStreamOutLayout"/> derived from this input layout.</returns> /// <remarks> /// <para> /// When streaming data out from the GPU, the layout of that data must be defined as a <see cref="GorgonStreamOutLayout"/>, which is quite similar to an input layout. For convenience, this method /// will create a new <see cref="GorgonStreamOutLayout"/> that uses the same semantics as the input layout. /// </para> /// </remarks> /// <seealso cref="GorgonStreamOutLayout"/> public GorgonStreamOutLayout ToStreamOutLayout(int stream = 0, byte slot = 0) { var elements = new GorgonStreamOutElement[_elements.Length]; for (int i = 0; i < _elements.Length; ++i) { // Only allow 64 elements. if (i > 64) { break; } GorgonInputElement inputElement = _elements[i]; var info = new GorgonFormatInfo(inputElement.Format); elements[i] = new GorgonStreamOutElement(inputElement.Context, 0, (byte)info.ComponentCount, slot, inputElement.Index, stream); } return(new GorgonStreamOutLayout(Name + " (SO)", elements)); }
/// <summary> /// Initializes a new instance of the <see cref="GorgonDepthStencil2DView"/> class. /// </summary> /// <param name="texture">The resource to bind to the view.</param> /// <param name="format">The format of the view.</param> /// <param name="formatInfo">Information about the format.</param> /// <param name="mipSlice">The mip level to use for the view.</param> /// <param name="firstArrayIndex">The first array index to use for the view.</param> /// <param name="arrayCount">The number of array indices to use for the view.</param> /// <param name="flags">Depth/stencil view flags.</param> internal GorgonDepthStencil2DView(GorgonTexture2D texture, BufferFormat format, GorgonFormatInfo formatInfo, int mipSlice, int firstArrayIndex, int arrayCount, DepthStencilViewFlags flags) : base(texture) { Texture = texture ?? throw new ArgumentNullException(nameof(texture)); Format = format; FormatInformation = formatInfo ?? throw new ArgumentNullException(nameof(formatInfo)); MipSlice = texture.MipLevels <= 0 ? 0 : mipSlice.Max(0).Min(texture.MipLevels - 1); ArrayIndex = firstArrayIndex; ArrayCount = arrayCount; Flags = flags; MipWidth = (Width >> MipSlice).Max(1); MipHeight = (Height >> MipSlice).Max(1); Bounds = new DX.Rectangle(0, 0, Width, Height); }
/// <summary> /// Function to save the specified image file. /// </summary> /// <param name="name">The name of the file to write into.</param> /// <param name="image">The image to save.</param> /// <param name="pixelFormat">The pixel format for the image.</param> /// <param name="codec">[Optional] The codec to use when saving the image.</param> /// <returns>The updated working file.</returns> public IGorgonVirtualFile SaveImageFile(string name, IGorgonImage image, BufferFormat pixelFormat, IGorgonImageCodec codec = null) { if (codec == null) { codec = DefaultCodec; } // We absolutely need to have an extension, or else the texconv tool will not work. if ((codec.CodecCommonExtensions.Count > 0) && (!string.Equals(Path.GetExtension(name), codec.CodecCommonExtensions[0], StringComparison.OrdinalIgnoreCase))) { _log.Print("Adding extension to working file or else external tools may not be able to read it.", LoggingLevel.Verbose); name = Path.ChangeExtension(name, codec.CodecCommonExtensions[0]); } IGorgonVirtualFile workFile = ScratchArea.FileSystem.GetFile(name); var formatInfo = new GorgonFormatInfo(pixelFormat); // The file doesn't exist, so we need to create a dummy file. if (workFile == null) { using (Stream tempStream = ScratchArea.OpenStream(name, FileMode.Create)) { tempStream.WriteString("TEMP_WORKING_FILE"); } workFile = ScratchArea.FileSystem.GetFile(name); } _log.Print($"Working image file: '{workFile.FullPath}'.", LoggingLevel.Verbose); IGorgonVirtualFile result; // For compressed images, we need to rely on an external tool to do the job. if (formatInfo.IsCompressed) { _log.Print($"Pixel format [{pixelFormat}] is a block compression format, compressing using external tool...", LoggingLevel.Intermediate); if ((_compressor == null) || (!codec.SupportsBlockCompression)) { throw new GorgonException(GorgonResult.CannotRead, string.Format(Resources.GORIMG_ERR_COMPRESSED_FILE, formatInfo.Format)); } // Send the image data as uncompressed to our working file, so we can have something to compress. using (Stream outStream = ScratchArea.OpenStream(workFile.FullPath, FileMode.Create)) { codec.SaveToStream(image, outStream); } _log.Print($"Saving to working file '{workFile.FullPath}'...", LoggingLevel.Simple); result = _compressor.Compress(workFile, pixelFormat, image.MipCount); // Convert to an uncompressed format if we aren't already in that format. switch (pixelFormat) { case BufferFormat.BC5_SNorm when image.Format != BufferFormat.R8G8_SNorm: image.ConvertToFormat(BufferFormat.R8G8_SNorm); break; case BufferFormat.BC5_Typeless when image.Format != BufferFormat.R8G8_UNorm: case BufferFormat.BC5_UNorm when image.Format != BufferFormat.R8G8_UNorm: image.ConvertToFormat(BufferFormat.R8G8_UNorm); break; case BufferFormat.BC6H_Sf16 when image.Format != BufferFormat.R16G16B16A16_Float: case BufferFormat.BC6H_Typeless when image.Format != BufferFormat.R16G16B16A16_Float: case BufferFormat.BC6H_Uf16 when image.Format != BufferFormat.R16G16B16A16_Float: image.ConvertToFormat(BufferFormat.R16G16B16A16_Float); break; case BufferFormat.BC4_SNorm when image.Format != BufferFormat.R8G8_SNorm: image.ConvertToFormat(BufferFormat.R8_SNorm); break; case BufferFormat.BC4_Typeless when image.Format != BufferFormat.R8G8_UNorm: case BufferFormat.BC4_UNorm when image.Format != BufferFormat.R8G8_UNorm: image.ConvertToFormat(BufferFormat.R8_UNorm); break; case BufferFormat.BC1_Typeless when image.Format != BufferFormat.R8G8B8A8_UNorm: case BufferFormat.BC1_UNorm when image.Format != BufferFormat.R8G8B8A8_UNorm: case BufferFormat.BC2_Typeless when image.Format != BufferFormat.R8G8B8A8_UNorm: case BufferFormat.BC2_UNorm when image.Format != BufferFormat.R8G8B8A8_UNorm: case BufferFormat.BC3_Typeless when image.Format != BufferFormat.R8G8B8A8_UNorm: case BufferFormat.BC3_UNorm when image.Format != BufferFormat.R8G8B8A8_UNorm: case BufferFormat.BC7_Typeless when image.Format != BufferFormat.R8G8B8A8_UNorm: case BufferFormat.BC7_UNorm when image.Format != BufferFormat.R8G8B8A8_UNorm: image.ConvertToFormat(BufferFormat.R8G8B8A8_UNorm); break; case BufferFormat.BC1_UNorm_SRgb when image.Format != BufferFormat.R8G8B8A8_UNorm_SRgb: case BufferFormat.BC2_UNorm_SRgb when image.Format != BufferFormat.R8G8B8A8_UNorm_SRgb: case BufferFormat.BC3_UNorm_SRgb when image.Format != BufferFormat.R8G8B8A8_UNorm_SRgb: case BufferFormat.BC7_UNorm_SRgb when image.Format != BufferFormat.R8G8B8A8_UNorm_SRgb: image.ConvertToFormat(BufferFormat.R8G8B8A8_UNorm_SRgb); break; } if (result == null) { throw new GorgonException(GorgonResult.CannotRead, string.Format(Resources.GORIMG_ERR_COMPRESSED_FILE, formatInfo.Format)); } } else { // We've changed the pixel format, so convert prior to saving. if (pixelFormat != image.Format) { _log.Print($"Image pixel format [{image.Format}] is different than requested format of [{pixelFormat}], converting...", LoggingLevel.Intermediate); image.ConvertToFormat(pixelFormat); _log.Print($"Converted image '{workFile.Name}' to pixel format: [{pixelFormat}].", LoggingLevel.Simple); } _log.Print($"Saving to working file '{workFile.FullPath}'...", LoggingLevel.Simple); using (Stream outStream = ScratchArea.OpenStream(workFile.FullPath, FileMode.Create)) { codec.SaveToStream(image, outStream); } ScratchArea.FileSystem.Refresh(); result = ScratchArea.FileSystem.GetFile(workFile.FullPath); } return(result); }
/// <summary> /// Function to perform the copying of image data into the buffer. /// </summary> /// <param name="reader">A reader used to read the data from the source stream.</param> /// <param name="image">Image data.</param> /// <param name="conversionFlags">Flags used to convert the image.</param> private void CopyImageData(GorgonBinaryReader reader, IGorgonImage image, TGAConversionFlags conversionFlags) { // TGA only supports 1 array level, and 1 mip level, so we only need to get the first buffer. IGorgonImageBuffer buffer = image.Buffers[0]; // Determine how large a row is, in bytes. var formatInfo = new GorgonFormatInfo(image.Format); GorgonPitchLayout srcPitch = (conversionFlags & TGAConversionFlags.Expand) == TGAConversionFlags.Expand ? new GorgonPitchLayout(image.Width * 3, image.Width * 3 * image.Height) : formatInfo.GetPitchForFormat(image.Width, image.Height); unsafe { // Otherwise, allocate a buffer for conversion. byte *destPtr = (byte *)buffer.Data; // Adjust destination for inverted axes. if ((conversionFlags & TGAConversionFlags.InvertX) == TGAConversionFlags.InvertX) { destPtr += buffer.PitchInformation.RowPitch - formatInfo.SizeInBytes; } if ((conversionFlags & TGAConversionFlags.InvertY) != TGAConversionFlags.InvertY) { destPtr += (image.Height - 1) * buffer.PitchInformation.RowPitch; } // Used to counter the number of lines to force as opaque. int opaqueLineCount = 0; // The buffer used to hold an uncompressed scanline. GorgonNativeBuffer <byte> lineBuffer = null; try { for (int y = 0; y < image.Height; y++) { // Indicates that the scanline has an alpha of 0 for the entire run. bool lineHasZeroAlpha; if ((conversionFlags & TGAConversionFlags.RLE) == TGAConversionFlags.RLE) { lineHasZeroAlpha = ReadCompressed(reader, image.Width, destPtr, image.Format, conversionFlags); } else { // Read the current scanline into memory. if (lineBuffer == null) { lineBuffer = new GorgonNativeBuffer <byte>(srcPitch.RowPitch); } reader.ReadRange(lineBuffer, count: srcPitch.RowPitch); lineHasZeroAlpha = ReadUncompressed((byte *)lineBuffer, srcPitch.RowPitch, destPtr, image.Format, conversionFlags); } if ((lineHasZeroAlpha) && ((conversionFlags & TGAConversionFlags.SetOpaqueAlpha) == TGAConversionFlags.SetOpaqueAlpha)) { opaqueLineCount++; } // The components of the pixel data in a TGA file need swizzling for 32 bit. if (formatInfo.BitDepth == 32) { ImageUtilities.SwizzleScanline(destPtr, buffer.PitchInformation.RowPitch, destPtr, buffer.PitchInformation.RowPitch, image.Format, ImageBitFlags.None); } if ((conversionFlags & TGAConversionFlags.InvertY) != TGAConversionFlags.InvertY) { destPtr -= buffer.PitchInformation.RowPitch; } else { destPtr += buffer.PitchInformation.RowPitch; } } } finally { lineBuffer?.Dispose(); } if (opaqueLineCount != image.Height) { return; } // Set the alpha to opaque if we don't have any alpha values (i.e. alpha = 0 for all pixels). destPtr = (byte *)buffer.Data; for (int y = 0; y < image.Height; y++) { ImageUtilities.CopyScanline(destPtr, buffer.PitchInformation.RowPitch, destPtr, buffer.PitchInformation.RowPitch, image.Format, ImageBitFlags.OpaqueAlpha); destPtr += buffer.PitchInformation.RowPitch; } } }
/// <summary> /// Function to import an image file from the physical file system into the current image. /// </summary> /// <param name="codec">The codec used to open the file.</param> /// <param name="filePath">The path to the file to import.</param> /// <returns>The source file information, image data, the virtual file entry for the working file and the original pixel format of the file.</returns> public (FileInfo file, IGorgonImage image, IGorgonVirtualFile workingFile, BufferFormat originalFormat) ImportImage(IGorgonImageCodec codec, string filePath) { var file = new FileInfo(filePath); IGorgonImageCodec importCodec = codec; IGorgonImageInfo metaData = null; IGorgonVirtualFile workFile = null; IGorgonImage importImage = null; string workFilePath = $"{Path.GetFileNameWithoutExtension(filePath)}_import_{Guid.NewGuid().ToString("N")}"; // Try to determine if we can actually read the file using an installed codec, if we can't, then try to find a suitable codec. using (FileStream stream = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read)) { if ((importCodec == null) || (!importCodec.IsReadable(stream))) { importCodec = null; foreach (IGorgonImageCodec newCodec in InstalledCodecs.Codecs.Where(item => (item.CodecCommonExtensions.Count > 0) && (item.CanDecode))) { if (newCodec.IsReadable(stream)) { importCodec = newCodec; break; } } } if (importCodec == null) { throw new GorgonException(GorgonResult.CannotRead, string.Format(Resources.GORIMG_ERR_NO_CODEC, filePath)); } metaData = importCodec.GetMetaData(stream); // We absolutely need to have an extension, or else the texconv tool will not work. var codecExtension = new GorgonFileExtension(importCodec.CodecCommonExtensions[0]); _log.Print($"Adding {codecExtension.Extension} extension to working file or else external tools may not be able to read it.", LoggingLevel.Verbose); workFilePath = $"{workFilePath}.{codecExtension.Extension}"; using (Stream outStream = ScratchArea.OpenStream(workFilePath, FileMode.Create)) { stream.CopyTo(outStream); } } workFile = ScratchArea.FileSystem.GetFile(workFilePath); var formatInfo = new GorgonFormatInfo(metaData.Format); // This is always in DDS format. if (formatInfo.IsCompressed) { _log.Print($"Image is compressed using [{formatInfo.Format}] as its pixel format.", LoggingLevel.Intermediate); if (_compressor == null) { throw new GorgonException(GorgonResult.CannotRead, string.Format(Resources.GORIMG_ERR_COMPRESSED_FILE, formatInfo.Format)); } _log.Print($"Loading image '{workFile.FullPath}'...", LoggingLevel.Simple); importImage = _compressor.Decompress(ref workFile, metaData); if (importImage == null) { throw new GorgonException(GorgonResult.CannotRead, string.Format(Resources.GORIMG_ERR_COMPRESSED_FILE, formatInfo.Format)); } _log.Print($"Loaded compressed ([{formatInfo.Format}]) image data as [{importImage.Format}]", LoggingLevel.Intermediate); } else { using (Stream workStream = workFile.OpenStream()) { importImage = importCodec.LoadFromStream(workStream); } } return(file, importImage, workFile, metaData.Format); }
/// <summary> /// Function to persist a <see cref="IGorgonImage"/> to a stream. /// </summary> /// <param name="imageData">A <see cref="IGorgonImage"/> to persist to the stream.</param> /// <param name="stream">The stream that will receive the image data.</param> /// <exception cref="ArgumentNullException">Thrown when the <paramref name="stream"/>, or the <paramref name="imageData"/> parameter is <b>null</b>.</exception> /// <exception cref="ArgumentEmptyException">Thrown when the <paramref name="stream"/> is read only.</exception> /// <exception cref="NotSupportedException">Thrown when the image data in the stream has a pixel format that is unsupported by the codec.</exception> /// <remarks> /// <para> /// When persisting image data via a codec, the image must have a format that the codec can recognize. This list of supported formats is provided by the <see cref="SupportedPixelFormats"/> /// property. Applications may convert their image data a supported format before saving the data using a codec. /// </para> /// </remarks> public override void SaveToStream(IGorgonImage imageData, Stream stream) { // Ensure that we can actually read this format. We do not perform total pixel conversion on behalf of the user, they are responsible for that. // We will, however, support swizzling and pixel compression (e.g. 32 -> 24 bit). if (Array.IndexOf(_supportedFormats, imageData.Format) == -1) { throw new NotSupportedException(string.Format(Resources.GORIMG_ERR_FORMAT_NOT_SUPPORTED, imageData.Format)); } using (var writer = new GorgonBinaryWriter(stream, true)) { // Write the header for the file before we dump the file contents. TgaHeader header = GetHeader(imageData, out TGAConversionFlags conversionFlags); GorgonPitchLayout destPitch; if ((conversionFlags & TGAConversionFlags.RGB888) == TGAConversionFlags.RGB888) { destPitch = new GorgonPitchLayout(imageData.Width * 3, imageData.Width * 3 * imageData.Height); } else { var formatInfo = new GorgonFormatInfo(imageData.Format); destPitch = formatInfo.GetPitchForFormat(imageData.Width, imageData.Height); } GorgonPitchLayout srcPitch = imageData.Buffers[0].PitchInformation; // If the two pitches are equal and we have no conversion requirements, then just write out the buffer. if ((destPitch == srcPitch) && (conversionFlags == TGAConversionFlags.None)) { writer.WriteValue(ref header); writer.WriteRange(imageData.Buffers[0].Data, count: srcPitch.SlicePitch); return; } unsafe { // Get the pointer to the first mip/array/depth level. byte *srcPointer = (byte *)imageData.Buffers[0].Data; var lineBuffer = new GorgonNativeBuffer <byte>(srcPitch.RowPitch); try { // Persist the working buffer to the stream. writer.WriteValue(ref header); // Write out each scan line. for (int y = 0; y < imageData.Height; y++) { byte *destPtr = (byte *)lineBuffer; if ((conversionFlags & TGAConversionFlags.RGB888) == TGAConversionFlags.RGB888) { ImageUtilities.Compress24BPPScanLine(srcPointer, srcPitch.RowPitch, destPtr, destPitch.RowPitch, (conversionFlags & TGAConversionFlags.Swizzle) == TGAConversionFlags.Swizzle); } else if ((conversionFlags & TGAConversionFlags.Swizzle) == TGAConversionFlags.Swizzle) { ImageUtilities.SwizzleScanline(srcPointer, srcPitch.RowPitch, destPtr, destPitch.RowPitch, imageData.Format, ImageBitFlags.None); } else { ImageUtilities.CopyScanline(srcPointer, srcPitch.RowPitch, destPtr, destPitch.RowPitch, imageData.Format, ImageBitFlags.None); } srcPointer += srcPitch.RowPitch; writer.WriteRange(lineBuffer, count: destPitch.RowPitch); } } finally { lineBuffer?.Dispose(); } } } }
/// <summary> /// Initializes a new instance of the <see cref="GorgonRenderTarget2DView"/> class. /// </summary> /// <param name="resource">The resource to bind.</param> /// <param name="format">The format of the render target view.</param> /// <param name="formatInfo">Information about the format.</param> /// <exception cref="ArgumentNullException">Thrown when the <paramref name="resource"/>, or the <paramref name="formatInfo"/> parameter is <b>null</b>.</exception> protected GorgonRenderTargetView(GorgonGraphicsResource resource, BufferFormat format, GorgonFormatInfo formatInfo) : base(resource) { FormatInformation = formatInfo ?? throw new ArgumentNullException(nameof(formatInfo)); Format = format; }
/// <summary> /// Initializes a new instance of the <see cref="GorgonRenderTarget3DView" /> class. /// </summary> /// <param name="texture">The render target texture to bind.</param> /// <param name="format">The format of the render target view.</param> /// <param name="formatInfo">The format information.</param> /// <param name="mipSlice">The mip slice to use in the view.</param> /// <param name="startDepthSlice">The first depth slice to use in the view.</param> /// <param name="depthSliceCount">The number of depth slices to use in the view.</param> internal GorgonRenderTarget3DView(GorgonTexture3D texture, BufferFormat format, GorgonFormatInfo formatInfo, int mipSlice, int startDepthSlice, int depthSliceCount) : base(texture, format, formatInfo) { Texture = texture; MipSlice = mipSlice; StartDepthSlice = startDepthSlice; DepthSliceCount = depthSliceCount; MipWidth = (Width >> MipSlice).Max(1); MipHeight = (Height >> MipSlice).Max(1); Bounds = new DX.Rectangle(0, 0, Width, Height); }
/// <summary> /// Initializes a new instance of the <see cref="GorgonRenderTarget2DView" /> class. /// </summary> /// <param name="texture">The render target texture to bind.</param> /// <param name="format">The format of the render target view.</param> /// <param name="formatInfo">The format information.</param> /// <param name="mipSlice">The mip slice to use in the view.</param> /// <param name="arrayIndex">The first array index to use in the view.</param> /// <param name="arrayCount">The number of array indices to use in the view.</param> internal GorgonRenderTarget2DView(GorgonTexture2D texture, BufferFormat format, GorgonFormatInfo formatInfo, int mipSlice, int arrayIndex, int arrayCount) : base(texture, format, formatInfo) { Texture = texture; MipSlice = mipSlice; ArrayIndex = arrayIndex; ArrayCount = arrayCount; MipWidth = (Width >> MipSlice).Max(1); MipHeight = (Height >> MipSlice).Max(1); Bounds = new DX.Rectangle(0, 0, Width, Height); }