Exemple #1
0
 /// <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);
        }
Exemple #3
0
 /// <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;
 }
Exemple #4
0
 /// <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;
 }
Exemple #5
0
        /// <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);
        }
Exemple #6
0
        /// <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();
            }
        }
Exemple #7
0
        /// <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;
 }
Exemple #9
0
 /// <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;
 }
Exemple #10
0
 /// <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);
        }
Exemple #14
0
        /// <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);
        }
Exemple #16
0
        /// <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);
 }
Exemple #19
0
 /// <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);
 }