/// <summary> /// Function to create a vertex buffer and its binding. /// </summary> /// <typeparam name="T">The type of data representing a vertex, must be an unmanaged value type.</typeparam> /// <param name="graphics">The graphics interface that will create the buffer.</param> /// <param name="vertexCount">The total number vertices that the buffer can hold.</param> /// <param name="usage">[Optional] The intended usage for the buffer.</param> /// <param name="binding">[Optional] The binding options for the buffer.</param> /// <param name="initialData">[Optional] An initial set of vertex data to send to the buffer.</param> /// <param name="bindingIndex">[Optional] The index, in vertices, inside the buffer where binding is to begin.</param> /// <param name="bufferName">[Optional] A name for the buffer.</param> /// <returns>A new <see cref="GorgonVertexBufferBinding"/>.</returns> /// <exception cref="ArgumentNullException">Thrown when the <paramref name="graphics"/> parameter is <b>null</b>.</exception> /// <remarks> /// <para> /// Use this to quickly create a vertex buffer and its binding based on a known vertex data type. /// </para> /// <para> /// Be aware that the <see cref="VertexBuffer"/> created by this method must be disposed manually after it is no longer of any use. /// </para> /// </remarks> /// <seealso cref="GorgonVertexBuffer"/> public static GorgonVertexBufferBinding CreateVertexBuffer <T>(GorgonGraphics graphics, int vertexCount, ResourceUsage usage = ResourceUsage.Default, VertexIndexBufferBinding binding = VertexIndexBufferBinding.None, GorgonNativeBuffer <T> initialData = null, int bindingIndex = 0, string bufferName = null) where T : unmanaged { if (graphics == null) { throw new ArgumentNullException(nameof(graphics)); } int vertexSize = Unsafe.SizeOf <T>(); var buffer = new GorgonVertexBuffer(graphics, new GorgonVertexBufferInfo(bufferName) { SizeInBytes = vertexCount * vertexSize, Binding = binding, Usage = usage }, initialData?.Cast <byte>()); return(new GorgonVertexBufferBinding(buffer, vertexSize, bindingIndex * vertexSize)); }
/// <summary> /// Function to build the plane vertices. /// </summary> /// <param name="buffer">Buffer to populate.</param> /// <param name="size">The width and height of the plane.</param> /// <param name="textureCoordinates">The texture coordinates to apply to the plane.</param> /// <param name="columns">The number of columns to subdivide by.</param> /// <param name="rows">The number of rows to subdivide by.</param> private void GetVertices(GorgonNativeBuffer <Vertex3D> buffer, DX.Vector2 size, RectangleF textureCoordinates, int columns, int rows) { float columnWidth = 1.0f / columns; float columnHeight = 1.0f / rows; int offset = 0; DX.Vector3 vertexNormal = -DX.Vector3.UnitZ; DX.Vector3.TransformNormal(ref vertexNormal, ref _orientation, out vertexNormal); for (int y = 0; y <= rows; ++y) { for (int x = 0; x <= columns; ++x) { var vertexPos = new DX.Vector3(((x * columnWidth) - 0.5f) * size.X, ((y * columnHeight) - 0.5f) * size.Y, 0); DX.Vector3.TransformCoordinate(ref vertexPos, ref _orientation, out vertexPos); buffer[offset++] = new Vertex3D { Position = new DX.Vector4(vertexPos, 1.0f), Normal = vertexNormal, UV = new DX.Vector2((x * (textureCoordinates.Width / columns)) + textureCoordinates.X, (1.0f - (y * (textureCoordinates.Height / rows))) + textureCoordinates.Y) }; } } }
/// <summary> /// Initializes a new instance of the <see cref="Cube"/> class. /// </summary> /// <param name="graphics">The graphics object used to create the buffers needed by this object.</param> /// <param name="inputLayout">The input layout describing how a vertex is laid out.</param> public Cube(GorgonGraphics graphics, GorgonInputLayout inputLayout) { CubeVertex[] vertices = { new CubeVertex(new DX.Vector3(-0.5f, 0.5f, -0.5f), new DX.Vector3(0, 0, 0)), new CubeVertex(new DX.Vector3(0.5f, 0.5f, -0.5f), new DX.Vector3(1.0f, 1.0f, 0)), new CubeVertex(new DX.Vector3(0.5f, -0.5f, -0.5f), new DX.Vector3(0.0f, 1.0f, 0)), new CubeVertex(new DX.Vector3(-0.5f, -0.5f, -0.5f), new DX.Vector3(1.0f, 0.0f, 0)), new CubeVertex(new DX.Vector3(-0.5f, 0.5f, 0.5f), new DX.Vector3(0, 0, 0)), new CubeVertex(new DX.Vector3(0.5f, 0.5f, 0.5f), new DX.Vector3(1.0f, 1.0f, 0)), new CubeVertex(new DX.Vector3(0.5f, -0.5f, 0.5f), new DX.Vector3(0.0f, 1.0f, 0)), new CubeVertex(new DX.Vector3(-0.5f, -0.5f, 0.5f), new DX.Vector3(1.0f, 0.0f, 0)), }; ushort[] indices = { // Front face. 0, 1, 2, 2, 3, 0, // Back face. 5, 4, 6, 4, 7, 6, // Left face. 4, 0, 3, 3, 7, 4, // Right face. 1, 5, 6, 6, 2, 1, // Top face 4, 5, 1, 1, 0, 4, // Bottom face 2, 6, 7, 7, 3, 2 }; // Create our index buffer and vertex buffer and populate with our cube data. using (var indexPtr = GorgonNativeBuffer <ushort> .Pin(indices)) using (var vertexPtr = GorgonNativeBuffer <CubeVertex> .Pin(vertices)) { IndexBuffer = new GorgonIndexBuffer(graphics, new GorgonIndexBufferInfo("Volume Index Buffer") { Usage = ResourceUsage.Immutable, IndexCount = indices.Length, Use16BitIndices = true }, indexPtr); VertexBuffer = new GorgonVertexBufferBindings(inputLayout) { [0] = GorgonVertexBufferBinding.CreateVertexBuffer(graphics, vertices.Length, ResourceUsage.Immutable, initialData: vertexPtr, bufferName: "Volume Vertex Buffer") }; } }
/// <summary> /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// </summary> public void Dispose() { _preParsedData?.Dispose(); _preParsedData = null; GC.SuppressFinalize(this); }
/// <summary> /// Initializes a new instance of the <see cref="GorgonBuffer" /> class. /// </summary> /// <param name="graphics">The <see cref="GorgonGraphics"/> object used to create and manipulate the buffer.</param> /// <param name="info">Information used to create the buffer.</param> /// <param name="initialData">[Optional] The initial data used to populate the buffer.</param> /// <exception cref="ArgumentNullException">Thrown when the <paramref name="info"/> parameter is <b>null</b>.</exception> /// <exception cref="ArgumentException">Thrown if the size of the buffer is less than 1 byte.</exception> /// <exception cref="GorgonException">Thrown if the buffer is created with a usage of <see cref="ResourceUsage.Immutable"/>, but the <paramref name="initialData"/> parameter is <b>null</b>. /// <para>-or-</para> /// <para>A value on the <paramref name="info"/> parameter is incorrect.</para> /// </exception> public GorgonBuffer(GorgonGraphics graphics, IGorgonBufferInfo info, GorgonNativeBuffer <byte> initialData = null) : base(graphics) { _info = new GorgonBufferInfo(info ?? throw new ArgumentNullException(nameof(info))); Initialize(initialData); }
/// <summary> /// Initializes a new instance of the <see cref="Cube" /> class. /// </summary> /// <param name="graphics">The graphics interface.</param> /// <param name="size">The width, height and depth of the cube.</param> /// <param name="textureCoordinates">The texture coordinates for the faces of the cube.</param> /// <param name="angle">The initial orientation of the cube, in degrees.</param> /// <param name="columnsPerFace">The number of columns per face.</param> /// <param name="rowsPerFace">The number of rows per face.</param> public Cube(GorgonGraphics graphics, DX.Vector3 size, RectangleF textureCoordinates, DX.Vector3 angle, int columnsPerFace = 1, int rowsPerFace = 1) : base(graphics) { PrimitiveType = PrimitiveType.TriangleList; int faceVertexCount = (columnsPerFace + 1) * (rowsPerFace + 1); int faceIndexCount = (columnsPerFace * rowsPerFace) * 6; VertexCount = faceVertexCount * 6; IndexCount = faceIndexCount * 6; TriangleCount = IndexCount / 3; DX.Quaternion.RotationYawPitchRoll(angle.Y.ToRadians(), angle.X.ToRadians(), angle.Z.ToRadians(), out DX.Quaternion orientation); DX.Matrix.RotationQuaternion(ref orientation, out _orientation); using (var vertexData = new GorgonNativeBuffer <Vertex3D>(VertexCount)) using (var indexData = new GorgonNativeBuffer <int>(IndexCount)) { // Front. GetVertices(vertexData, 0, DX.Vector3.UnitY, -DX.Vector3.UnitZ, size, textureCoordinates, columnsPerFace, rowsPerFace); // Bottom. GetVertices(vertexData, faceVertexCount, -DX.Vector3.UnitZ, -DX.Vector3.UnitY, size, textureCoordinates, columnsPerFace, rowsPerFace); // Back. GetVertices(vertexData, faceVertexCount * 2, DX.Vector3.UnitY, DX.Vector3.UnitZ, size, textureCoordinates, columnsPerFace, rowsPerFace); // Top. GetVertices(vertexData, faceVertexCount * 3, DX.Vector3.UnitZ, DX.Vector3.UnitY, size, textureCoordinates, columnsPerFace, rowsPerFace); // Left. GetVertices(vertexData, faceVertexCount * 4, DX.Vector3.UnitY, -DX.Vector3.UnitX, size, textureCoordinates, columnsPerFace, rowsPerFace); // Right GetVertices(vertexData, faceVertexCount * 5, DX.Vector3.UnitY, DX.Vector3.UnitX, size, textureCoordinates, columnsPerFace, rowsPerFace); GetIndices(indexData, 0, 0, columnsPerFace, rowsPerFace); GetIndices(indexData, faceIndexCount, faceVertexCount, columnsPerFace, rowsPerFace); GetIndices(indexData, faceIndexCount * 2, faceVertexCount * 2, columnsPerFace, rowsPerFace); GetIndices(indexData, faceIndexCount * 3, faceVertexCount * 3, columnsPerFace, rowsPerFace); GetIndices(indexData, faceIndexCount * 4, faceVertexCount * 4, columnsPerFace, rowsPerFace); GetIndices(indexData, faceIndexCount * 5, faceVertexCount * 5, columnsPerFace, rowsPerFace); CalculateTangents(vertexData, indexData); VertexBuffer = new GorgonVertexBuffer(graphics, new GorgonVertexBufferInfo("CubeVB") { Usage = ResourceUsage.Immutable, SizeInBytes = vertexData.SizeInBytes }, vertexData.Cast <byte>()); IndexBuffer = new GorgonIndexBuffer(graphics, new GorgonIndexBufferInfo("CubeIB") { Usage = ResourceUsage.Immutable, Use16BitIndices = false, IndexCount = IndexCount }, indexData); } }
/// <summary> /// Function to return the object. /// </summary> /// <returns>The object created or updated by this builder.</returns> /// <exception cref="GorgonException">Thrown if the polygonal sprite has less than 3 vertices.</exception> /// <remarks> /// <para> /// This will return a <see cref="GorgonPolySprite"/> for use with the <see cref="Gorgon2D.DrawPolygonSprite"/> method. The object returned implements <see cref="IDisposable"/>, so it is the /// responsibility of the user to dispose of the object when they are done with it. /// </para> /// <para> /// <note type="warning"> /// <para> /// A polygon sprite must have a minimum of 3 vertices. If it does not, then this method will throw an exception. /// </para> /// </note> /// </para> /// </remarks> /// <seealso cref="GorgonPolySprite"/> /// <seealso cref="Gorgon2D"/> public GorgonPolySprite Build() { if (_workingSprite.RwVertices.Count < 3) { throw new GorgonException(GorgonResult.CannotCreate, Resources.GOR2D_ERR_POLY_SPRITE_NOT_ENOUGH_VERTS); } var newSprite = new GorgonPolySprite(); CopySprite(newSprite, _workingSprite); newSprite.Renderable.ActualVertexCount = newSprite.RwVertices.Count; if ((newSprite.Renderable.Vertices == null) || (newSprite.Renderable.Vertices.Length < newSprite.RwVertices.Count)) { newSprite.Renderable.Vertices = new Gorgon2DVertex[newSprite.RwVertices.Count]; } for (int i = 0; i < newSprite.RwVertices.Count; ++i) { newSprite.Renderable.Vertices[i] = newSprite.RwVertices[i].Vertex; } // Enforce clockwise ordering. _triangulator.EnsureWindingOrder(newSprite.Renderable.Vertices, WindingOrder.CounterClockwise); // Split the polygon hull into triangles. (GorgonNativeBuffer <int> indices, DX.RectangleF bounds) = _triangulator.Triangulate(newSprite.Renderable.Vertices, WindingOrder.CounterClockwise); GorgonNativeBuffer <Gorgon2DVertex> vertexData = newSprite.Renderable.Vertices.ToNativeBuffer(); try { newSprite.Renderable.IndexBuffer = new GorgonIndexBuffer(Graphics, new GorgonIndexBufferInfo { Binding = VertexIndexBufferBinding.None, Use16BitIndices = false, IndexCount = indices.Length, Usage = ResourceUsage.Immutable }, indices); newSprite.Renderable.VertexBuffer = GorgonVertexBufferBinding.CreateVertexBuffer(Graphics, new GorgonVertexBufferInfo { Usage = ResourceUsage.Immutable, Binding = VertexIndexBufferBinding.None, SizeInBytes = Gorgon2DVertex.SizeInBytes * newSprite.RwVertices.Count }, vertexData); newSprite.Renderable.ActualVertexCount = newSprite.RwVertices.Count; newSprite.Renderable.IndexCount = indices.Length; newSprite.Bounds = new DX.RectangleF(newSprite.Position.X, newSprite.Position.Y, bounds.Width, bounds.Height); } finally { vertexData?.Dispose(); indices?.Dispose(); } return(newSprite); }
/// <summary> /// Function to initialize the buffer data. /// </summary> /// <param name="initialData">The initial data used to populate the buffer.</param> private void Initialize <T>(GorgonNativeBuffer <T> initialData) where T : unmanaged { D3D11.CpuAccessFlags cpuFlags = GetCpuFlags(false, D3D11.BindFlags.IndexBuffer); Log.Print($"{Name} Index Buffer: Creating D3D11 buffer. Size: {SizeInBytes} bytes", LoggingLevel.Simple); GorgonVertexBuffer.ValidateBufferBindings(_info.Usage, 0); D3D11.BindFlags bindFlags = D3D11.BindFlags.IndexBuffer; if ((_info.Binding & VertexIndexBufferBinding.StreamOut) == VertexIndexBufferBinding.StreamOut) { bindFlags |= D3D11.BindFlags.StreamOutput; } if ((_info.Binding & VertexIndexBufferBinding.UnorderedAccess) == VertexIndexBufferBinding.UnorderedAccess) { bindFlags |= D3D11.BindFlags.UnorderedAccess; } var desc = new D3D11.BufferDescription { SizeInBytes = SizeInBytes, Usage = (D3D11.ResourceUsage)_info.Usage, BindFlags = bindFlags, OptionFlags = D3D11.ResourceOptionFlags.None, CpuAccessFlags = cpuFlags, StructureByteStride = 0 }; if ((initialData != null) && (initialData.Length > 0)) { unsafe { D3DResource = Native = new D3D11.Buffer(Graphics.D3DDevice, new IntPtr((void *)initialData), desc) { DebugName = Name }; } } else { D3DResource = Native = new D3D11.Buffer(Graphics.D3DDevice, desc) { DebugName = Name }; } }
/// <summary> /// Initializes a new instance of the <see cref="Plane" /> class. /// </summary> /// <param name="graphics">The graphics interface used to create the buffers for this object.</param> /// <param name="inputLayout">The input layout for the vertices in this mesh.</param> /// <param name="size">The width and height of the plane.</param> /// <param name="textureCoordinates">Texture coordinates.</param> public Plane(GorgonGraphics graphics, GorgonInputLayout inputLayout, DX.Vector2 size, DX.RectangleF textureCoordinates) : base(inputLayout) { Size = size; // Create our vertices. Vertices = new[] { new BoingerVertex(new DX.Vector3(-size.X, size.Y, 0.0f), textureCoordinates.Location), new BoingerVertex(new DX.Vector3(size.X, size.Y, 0.0f), new DX.Vector2(textureCoordinates.Right, textureCoordinates.Top)), new BoingerVertex(new DX.Vector3(-size.X, -size.Y, 0.0f), new DX.Vector2(textureCoordinates.Left, textureCoordinates.Bottom)), new BoingerVertex(new DX.Vector3(size.X, -size.Y, 0.0f), new DX.Vector2(textureCoordinates.Right, textureCoordinates.Bottom)) }; // Create our indices. Indices = new ushort[] { 0, 1, 2, 2, 1, 3 }; // Copy the above vertex/index data into a vertex and index buffer so we can render our plane. using (var vertexPtr = GorgonNativeBuffer <BoingerVertex> .Pin(Vertices)) using (var indexPtr = GorgonNativeBuffer <ushort> .Pin(Indices)) { VertexBufferBindings[0] = GorgonVertexBufferBinding.CreateVertexBuffer(graphics, new GorgonVertexBufferInfo("Plane Vertex Buffer") { SizeInBytes = Vertices.Length * BoingerVertex.Size, Usage = ResourceUsage.Immutable }, vertexPtr); IndexBuffer = new GorgonIndexBuffer(graphics, new GorgonIndexBufferInfo("Plane Index Buffer") { Usage = ResourceUsage.Immutable, IndexCount = Indices.Length, Use16BitIndices = true }, indexPtr); } }
/// <summary> /// Function to build the plane indices. /// </summary> /// <param name="buffer">Buffer to populate.</param> /// <param name="offset">The offset into the buffer.</param> /// <param name="vertexStart">The starting vertex.</param> /// <param name="columns">Number of columns for the plane.</param> /// <param name="rows">Number of rows for the plane.</param> private static void GetIndices(GorgonNativeBuffer <int> buffer, int offset, int vertexStart, int columns, int rows) { int columnWrap = columns + 1; for (int row = 0; row < rows; ++row) { for (int column = 0; column < columns; ++column) { buffer[offset++] = (column + (row * columnWrap)) + vertexStart; buffer[offset++] = (column + ((row + 1) * columnWrap)) + vertexStart; buffer[offset++] = ((column + 1) + (row * columnWrap)) + vertexStart; buffer[offset++] = (column + ((row + 1) * columnWrap)) + vertexStart; buffer[offset++] = ((column + 1) + ((row + 1) * columnWrap)) + vertexStart; buffer[offset++] = ((column + 1) + (row * columnWrap)) + vertexStart; } } }
/// <summary> /// Function to build the plane vertices. /// </summary> /// <param name="buffer">Buffer to populate.</param> /// <param name="vertexOffset">The offset into the buffer.</param> /// <param name="up">Up vector for orientation.</param> /// <param name="normal">The face normal.</param> /// <param name="size">The width and height of the plane.</param> /// <param name="textureCoordinates">The texture coordinates to apply to the plane.</param> /// <param name="columns">The number of columns to subdivide by.</param> /// <param name="rows">The number of rows to subdivide by.</param> private void GetVertices(GorgonNativeBuffer <Vertex3D> buffer, int vertexOffset, DX.Vector3 up, DX.Vector3 normal, DX.Vector3 size, RectangleF textureCoordinates, int columns, int rows) { float columnWidth = 1.0f / columns; float columnHeight = 1.0f / rows; DX.Matrix rotation = DX.Matrix.Identity; DX.Vector3.Cross(ref normal, ref up, out DX.Vector3 orientVector); DX.Vector3.Multiply(ref normal, 0.5f, out DX.Vector3 translate); rotation.Row1 = (DX.Vector4)orientVector; rotation.Row2 = (DX.Vector4)up; rotation.Row3 = (DX.Vector4)normal; rotation.Row4 = new DX.Vector4(translate, 1); DX.Vector3.TransformCoordinate(ref normal, ref _orientation, out DX.Vector3 transformNormal); DX.Vector3.Normalize(ref transformNormal, out transformNormal); for (int y = 0; y <= rows; ++y) { for (int x = 0; x <= columns; ++x) { var vertexPos = new DX.Vector3(((x * columnWidth) - 0.5f) * size.X, ((y * columnHeight) - 0.5f) * size.Y, 0); DX.Vector3.TransformCoordinate(ref vertexPos, ref rotation, out vertexPos); DX.Vector3.TransformCoordinate(ref vertexPos, ref _orientation, out vertexPos); buffer[vertexOffset++] = new Vertex3D { Position = new DX.Vector4(vertexPos, 1.0f), Normal = transformNormal, UV = new DX.Vector2((x * (textureCoordinates.Width / columns)) + textureCoordinates.X, (1.0f - (y * (textureCoordinates.Height / rows))) + textureCoordinates.Y) }; } } }
/// <summary> /// Initializes a new instance of the <see cref="Sphere" /> class. /// </summary> /// <param name="graphics">Graphics interface to use.</param> /// <param name="radius">Radius of the sphere</param> /// <param name="textureCoordinates">The texture coordinates to apply to the sphere.</param> /// <param name="angle">The angle of rotation, in degrees.</param> /// <param name="ringCount">Number of rings in the sphere.</param> /// <param name="segmentCount">Number of segments in the sphere.</param> public Sphere(GorgonGraphics graphics, float radius, RectangleF textureCoordinates, DX.Vector3 angle, int ringCount = 8, int segmentCount = 16) : base(graphics) { // Calculate number of vertices and indices required for our sphere. PrimitiveType = PrimitiveType.TriangleList; VertexCount = (ringCount + 1) * (segmentCount + 1); IndexCount = 6 * ringCount * (segmentCount + 1); TriangleCount = IndexCount / 3; DX.Quaternion.RotationYawPitchRoll(angle.Y.ToRadians(), angle.X.ToRadians(), angle.Z.ToRadians(), out DX.Quaternion orientation); DX.Matrix.RotationQuaternion(ref orientation, out _orientation); using (var vertexData = new GorgonNativeBuffer <Vertex3D>(VertexCount)) using (var indexData = new GorgonNativeBuffer <int>(IndexCount)) { GetVertices(vertexData, indexData, radius, textureCoordinates, ringCount, segmentCount); VertexBuffer = new GorgonVertexBuffer(graphics, new GorgonVertexBufferInfo("SphereVertexBuffer") { Usage = ResourceUsage.Immutable, SizeInBytes = vertexData.SizeInBytes }, vertexData.Cast <byte>()); IndexBuffer = new GorgonIndexBuffer(graphics, new GorgonIndexBufferInfo("SphereIndexBuffer") { Usage = ResourceUsage.Immutable, Use16BitIndices = false, IndexCount = IndexCount }, indexData); } }
/// <summary> /// Initializes a new instance of the <see cref="Triangle" /> class. /// </summary> /// <param name="graphics">The graphics interface.</param> /// <param name="point1">The 1st point in the triangle.</param> /// <param name="point2">The 2nd point in the triangle.</param> /// <param name="point3">The 3rd point in the triangle.</param> public Triangle(GorgonGraphics graphics, Vertex3D point1, Vertex3D point2, Vertex3D point3) : base(graphics) { PrimitiveType = PrimitiveType.TriangleList; VertexCount = 3; IndexCount = 3; TriangleCount = 1; point1.Tangent = new DX.Vector4(1.0f, 0, 0, 1.0f); point2.Tangent = new DX.Vector4(1.0f, 0, 0, 1.0f); point3.Tangent = new DX.Vector4(1.0f, 0, 0, 1.0f); using (var points = new GorgonNativeBuffer <Vertex3D>(3)) using (var indices = new GorgonNativeBuffer <int>(3)) { points[0] = point1; points[1] = point2; points[2] = point3; indices[0] = 0; indices[1] = 1; indices[2] = 2; VertexBuffer = new GorgonVertexBuffer(graphics, new GorgonVertexBufferInfo("TriVB") { Usage = ResourceUsage.Immutable, SizeInBytes = Vertex3D.Size * 3 }, points.Cast <byte>()); IndexBuffer = new GorgonIndexBuffer(graphics, new GorgonIndexBufferInfo("TriIB") { Usage = ResourceUsage.Dynamic, Use16BitIndices = false, IndexCount = 3 }, indices); } }
/// <summary> /// Function used to initalize the buffer. /// </summary> /// <param name="initialData">The data to copy into the buffer on creation.</param> private void Initialize(GorgonNativeBuffer <byte> initialData) { if (_info.SizeInBytes < 1) { throw new ArgumentException(string.Format(Resources.GORGFX_ERR_BUFFER_SIZE_TOO_SMALL, 1)); } if ((_info.Usage == ResourceUsage.Immutable) && (initialData == null)) { throw new GorgonException(GorgonResult.CannotCreate, Resources.GORGFX_ERR_BUFFER_IMMUTABLE_REQUIRES_DATA); } D3D11.BufferDescription desc = BuildBufferDesc(_info); // Implicitly allow reading for staging resources. if (_info.Usage == ResourceUsage.Staging) { _info.AllowCpuRead = true; } Log.Print($"{Name} Generic Buffer: Creating D3D11 buffer. Size: {SizeInBytes} bytes", LoggingLevel.Simple); if ((initialData != null) && (initialData.Length > 0)) { unsafe { D3DResource = Native = new D3D11.Buffer(Graphics.D3DDevice, new IntPtr((byte *)initialData), desc) { DebugName = $"{Name}_ID3D11Buffer" }; } } else { D3DResource = Native = new D3D11.Buffer(Graphics.D3DDevice, desc) { DebugName = $"{Name}_ID3D11Buffer" }; } }
/// <summary> /// Function to initialize the buffer data. /// </summary> /// <param name="initialData">The initial data used to populate the buffer.</param> private void Initialize(GorgonNativeBuffer <byte> initialData) { // If the buffer is not aligned to 16 bytes, then pad the size. _info.SizeInBytes = (_info.SizeInBytes + 15) & ~15; TotalConstantCount = _info.SizeInBytes / (sizeof(float) * 4); D3D11.CpuAccessFlags cpuFlags = GetCpuFlags(false, D3D11.BindFlags.ConstantBuffer); Log.Print($"{Name} Constant Buffer: Creating D3D11 buffer. Size: {_info.SizeInBytes} bytes", LoggingLevel.Simple); var desc = new D3D11.BufferDescription { SizeInBytes = _info.SizeInBytes, Usage = (D3D11.ResourceUsage)_info.Usage, BindFlags = D3D11.BindFlags.ConstantBuffer, OptionFlags = D3D11.ResourceOptionFlags.None, CpuAccessFlags = cpuFlags, StructureByteStride = 0 }; if ((initialData != null) && (initialData.Length > 0)) { unsafe { D3DResource = Native = new D3D11.Buffer(Graphics.D3DDevice, new IntPtr((void *)initialData), desc) { DebugName = Name }; } } else { D3DResource = Native = new D3D11.Buffer(Graphics.D3DDevice, desc) { DebugName = Name }; } }
/// <summary> /// Function to create the vertex/index buffers for the renderer. /// </summary> private void CreateBuffers() { // We don't need to update the index buffer ever. So we can set up the indices right now. using (var indices = new GorgonNativeBuffer <ushort>(MaxSpriteCount * 6)) { int indexOffset = 0; ushort index = 0; for (int i = 0; i < MaxSpriteCount; ++i) { indices[indexOffset++] = index; indices[indexOffset++] = (ushort)(index + 1); indices[indexOffset++] = (ushort)(index + 2); indices[indexOffset++] = (ushort)(index + 1); indices[indexOffset++] = (ushort)(index + 3); indices[indexOffset++] = (ushort)(index + 2); index += 4; } VertexBuffer = GorgonVertexBufferBinding.CreateVertexBuffer <Gorgon2DVertex>(Graphics, new GorgonVertexBufferInfo { Usage = ResourceUsage.Dynamic, Binding = VertexIndexBufferBinding.None, SizeInBytes = Gorgon2DVertex.SizeInBytes * (MaxSpriteCount * 4) }); IndexBuffer = new GorgonIndexBuffer(Graphics, new GorgonIndexBufferInfo { Usage = ResourceUsage.Immutable, Binding = VertexIndexBufferBinding.None, IndexCount = indices.Length }, indices); } }
/// <summary> /// Initializes a new instance of the <see cref="Plane" /> class. /// </summary> /// <param name="graphics">The graphics interface to use.</param> /// <param name="size">The width and height of the plane.</param> /// <param name="textureCoordinates">The texture coordinates to apply to the plane.</param> /// <param name="angle">The initial orientation, in degrees.</param> /// <param name="columns">The number of columns to subdivide by.</param> /// <param name="rows">The number of rows to subdivide by.</param> public Plane(GorgonGraphics graphics, DX.Vector2 size, RectangleF textureCoordinates, DX.Vector3 angle, int columns = 1, int rows = 1) : base(graphics) { PrimitiveType = PrimitiveType.TriangleStrip; VertexCount = (columns + 1) * (rows + 1); IndexCount = ((columns * rows) * 6) + (rows - 1); TriangleCount = (IndexCount - (rows - 1)) / 3; DX.Quaternion.RotationYawPitchRoll(angle.Y.ToRadians(), angle.X.ToRadians(), angle.Z.ToRadians(), out DX.Quaternion orientation); DX.Matrix.RotationQuaternion(ref orientation, out _orientation); using (var vertexData = new GorgonNativeBuffer <Vertex3D>(VertexCount)) using (var indexData = new GorgonNativeBuffer <int>(IndexCount)) { GetVertices(vertexData, size, textureCoordinates, columns, rows); GetIndices(indexData, columns, rows); CalculateTangents(vertexData, indexData); VertexBuffer = new GorgonVertexBuffer(graphics, new GorgonVertexBufferInfo("PlaneVB") { Usage = ResourceUsage.Immutable, SizeInBytes = vertexData.SizeInBytes }, vertexData.Cast <byte>()); IndexBuffer = new GorgonIndexBuffer(graphics, new GorgonIndexBufferInfo { Usage = ResourceUsage.Immutable, Use16BitIndices = false, IndexCount = IndexCount }, indexData); } }
/// <summary> /// Function to build the plane indices. /// </summary> /// <param name="buffer">Buffer to populate.</param> /// <param name="columns">Number of columns for the plane.</param> /// <param name="rows">Number of rows for the plane.</param> private static void GetIndices(GorgonNativeBuffer <int> buffer, int columns, int rows) { int offset = 0; int columnWrap = columns + 1; for (int row = 0; row < rows; ++row) { for (int column = 0; column < columns; ++column) { buffer[offset++] = column + (row * columnWrap); buffer[offset++] = column + ((row + 1) * columnWrap); buffer[offset++] = (column + 1) + (row * columnWrap); buffer[offset++] = column + ((row + 1) * columnWrap); buffer[offset++] = (column + 1) + (row * columnWrap); buffer[offset++] = (column + 1) + ((row + 1) * columnWrap); } if (row < rows - 1) { buffer[offset++] = unchecked ((int)0xffffffff); } } }
/// <summary> /// Initializes a new instance of the <see cref="GorgonIndexBuffer" /> class, initialized with <see cref="ushort"/> values for 16 bit index buffers. /// </summary> /// <param name="graphics">The <see cref="GorgonGraphics"/> object used to create and manipulate the buffer.</param> /// <param name="info">Information used to create the buffer.</param> /// <param name="initialData">The initial data used to populate the buffer.</param> /// <exception cref="ArgumentNullException">Thrown when the <paramref name="graphics"/>, <paramref name="info"/> or the <paramref name="initialData"/> parameters are <b>null</b>.</exception> public GorgonIndexBuffer(GorgonGraphics graphics, IGorgonIndexBufferInfo info, GorgonNativeBuffer <ushort> initialData) : base(graphics) { _info = new GorgonIndexBufferInfo(info ?? throw new ArgumentNullException(nameof(info))); Initialize(initialData ?? throw new ArgumentNullException(nameof(initialData))); }
/// <summary> /// Initializes a new instance of the <see cref="Cube"/> class. /// </summary> /// <param name="graphics">The graphics object used to create the buffers needed by this object.</param> /// <param name="inputLayout">The input layout describing how a vertex is laid out.</param> public Cube(GorgonGraphics graphics, GorgonInputLayout inputLayout) { GlassCubeVertex[] vertices = { // Front face. new GlassCubeVertex(new DX.Vector3(-0.5f, 0.5f, -0.5f), new DX.Vector2(0, 0)), new GlassCubeVertex(new DX.Vector3(0.5f, -0.5f, -0.5f), new DX.Vector2(1.0f, 1.0f)), new GlassCubeVertex(new DX.Vector3(-0.5f, -0.5f, -0.5f), new DX.Vector2(0.0f, 1.0f)), new GlassCubeVertex(new DX.Vector3(0.5f, 0.5f, -0.5f), new DX.Vector2(1.0f, 0.0f)), // Right face. new GlassCubeVertex(new DX.Vector3(0.5f, 0.5f, -0.5f), new DX.Vector2(0, 0)), new GlassCubeVertex(new DX.Vector3(0.5f, -0.5f, 0.5f), new DX.Vector2(1.0f, 1.0f)), new GlassCubeVertex(new DX.Vector3(0.5f, -0.5f, -0.5f), new DX.Vector2(0.0f, 1.0f)), new GlassCubeVertex(new DX.Vector3(0.5f, 0.5f, 0.5f), new DX.Vector2(1.0f, 0.0f)), // Back face. new GlassCubeVertex(new DX.Vector3(0.5f, 0.5f, 0.5f), new DX.Vector2(0, 0)), new GlassCubeVertex(new DX.Vector3(-0.5f, -0.5f, 0.5f), new DX.Vector2(1.0f, 1.0f)), new GlassCubeVertex(new DX.Vector3(0.5f, -0.5f, 0.5f), new DX.Vector2(0.0f, 1.0f)), new GlassCubeVertex(new DX.Vector3(-0.5f, 0.5f, 0.5f), new DX.Vector2(1.0f, 0.0f)), // Left face. new GlassCubeVertex(new DX.Vector3(-0.5f, 0.5f, 0.5f), new DX.Vector2(0, 0)), new GlassCubeVertex(new DX.Vector3(-0.5f, -0.5f, -0.5f), new DX.Vector2(1.0f, 1.0f)), new GlassCubeVertex(new DX.Vector3(-0.5f, -0.5f, 0.5f), new DX.Vector2(0.0f, 1.0f)), new GlassCubeVertex(new DX.Vector3(-0.5f, 0.5f, -0.5f), new DX.Vector2(1.0f, 0.0f)), // Top face. new GlassCubeVertex(new DX.Vector3(-0.5f, 0.5f, 0.5f), new DX.Vector2(0, 0)), new GlassCubeVertex(new DX.Vector3(0.5f, 0.5f, -0.5f), new DX.Vector2(1.0f, 1.0f)), new GlassCubeVertex(new DX.Vector3(-0.5f, 0.5f, -0.5f), new DX.Vector2(0.0f, 1.0f)), new GlassCubeVertex(new DX.Vector3(0.5f, 0.5f, 0.5f), new DX.Vector2(1.0f, 0.0f)), // Bottom face. new GlassCubeVertex(new DX.Vector3(-0.5f, -0.5f, -0.5f), new DX.Vector2(0, 0)), new GlassCubeVertex(new DX.Vector3(0.5f, -0.5f, 0.5f), new DX.Vector2(1.0f, 1.0f)), new GlassCubeVertex(new DX.Vector3(-0.5f, -0.5f, 0.5f), new DX.Vector2(0.0f, 1.0f)), new GlassCubeVertex(new DX.Vector3(0.5f, -0.5f, -0.5f), new DX.Vector2(1.0f, 0.0f)) }; ushort[] indices = { 8, 9, 10, 8, 11, 9, 12, 13, 14, 12, 15, 13, 4, 5, 6, 4, 7, 5, 16, 17, 18, 16, 19, 17, 20, 21, 22, 20, 23, 21, 0, 1, 2, 0, 3, 1 }; // Create our index buffer and vertex buffer and populate with our cube data. using (var indexPtr = GorgonNativeBuffer <ushort> .Pin(indices)) using (var vertexPtr = GorgonNativeBuffer <GlassCubeVertex> .Pin(vertices)) { IndexBuffer = new GorgonIndexBuffer(graphics, new GorgonIndexBufferInfo("GlassCube Index Buffer") { Usage = ResourceUsage.Immutable, IndexCount = indices.Length, Use16BitIndices = true }, indexPtr); VertexBuffer = new GorgonVertexBufferBindings(inputLayout) { [0] = GorgonVertexBufferBinding.CreateVertexBuffer(graphics, vertices.Length, ResourceUsage.Immutable, initialData: vertexPtr, bufferName: "GlassCube Vertex Buffer") }; } }
/// <summary> /// Initializes a new instance of the <see cref="Sphere" /> class. /// </summary> /// <param name="graphics">The graphics interface used to create the buffers for this object.</param> /// <param name="inputLayout">The input layout for the vertices in this mesh.</param> /// <param name="radius">Radius of the sphere</param> /// <param name="textureOffset">Offset of the texture.</param> /// <param name="textureScale">Scale of the texture.</param> /// <param name="ringCount">Number of rings in the sphere.</param> /// <param name="segmentCount">Number of segments in the sphere.</param> public Sphere(GorgonGraphics graphics, GorgonInputLayout inputLayout, float radius, DX.Vector2 textureOffset, DX.Size2F textureScale, int ringCount = 8, int segmentCount = 16) : base(inputLayout) { ushort index = 0; // Current index. int vertexIndex = 0; // Current vertex index. int indexIndex = 0; // Current index array index. float deltaRingAngle = ((float)System.Math.PI) / ringCount; float deltaSegAngle = (((float)System.Math.PI) * 2.0f) / segmentCount; // Calculate number of vertices and indices required for our sphere. int vertexCount = (ringCount + 1) * (segmentCount + 1); int indexCount = 6 * ringCount * (segmentCount + 1); Vertices = new BoingerVertex[vertexCount]; Indices = new ushort[indexCount]; Radius = radius; // Build our sphere. for (int ring = 0; ring <= ringCount; ring++) { float angle = deltaRingAngle * ring; float ringSin = angle.Sin(); var position = new DX.Vector3(0, angle.Cos() * radius, 0); for (int segment = 0; segment <= segmentCount; segment++) { var textureDelta = new DX.Vector2(1.0f - (segment / (float)segmentCount), 1.0f - (ring / (float)ringCount)); float segmentAngle = deltaSegAngle * segment; position.X = ringSin * segmentAngle.Sin() * radius; position.Z = ringSin * segmentAngle.Cos() * radius; // Create the vertex. textureDelta.X *= textureScale.Width; textureDelta.Y *= textureScale.Height; textureDelta.X += textureOffset.X; textureDelta.Y += textureOffset.Y; Vertices[vertexIndex++] = new BoingerVertex( position, textureDelta ); // Add the indices and skip the last ring. if (ring == ringCount) { continue; } Indices[indexIndex++] = (ushort)(index + segmentCount + 1); Indices[indexIndex++] = index; Indices[indexIndex++] = (ushort)(index + segmentCount); Indices[indexIndex++] = (ushort)(index + segmentCount + 1); Indices[indexIndex++] = (ushort)(index + 1); Indices[indexIndex++] = index; index++; } } // Copy the above vertex/index data into a vertex and index buffer so we can render our sphere. using (var indexPtr = GorgonNativeBuffer <ushort> .Pin(Indices)) using (var vertexPtr = GorgonNativeBuffer <BoingerVertex> .Pin(Vertices)) { VertexBufferBindings[0] = GorgonVertexBufferBinding.CreateVertexBuffer(graphics, new GorgonVertexBufferInfo("Sphere Vertex Buffer") { SizeInBytes = Vertices.Length * BoingerVertex.Size, Usage = ResourceUsage.Immutable }, vertexPtr); IndexBuffer = new GorgonIndexBuffer(graphics, new GorgonIndexBufferInfo("Sphere Index Buffer") { Usage = ResourceUsage.Immutable, IndexCount = Indices.Length }, indexPtr); } }
/// <summary> /// Function to calculate tangent information for bump mapping. /// </summary> /// <param name="vertexData">Buffer holding the vertices.</param> /// <param name="indexData">Buffer holding the indices.</param> protected void CalculateTangents(GorgonNativeBuffer <Vertex3D> vertexData, GorgonNativeBuffer <int> indexData) { var biTanData = new DX.Vector3[VertexCount]; var tanData = new DX.Vector3[VertexCount]; int indexOffset = 0; for (int i = 0; i < TriangleCount; ++i) { int index1 = indexData[indexOffset++]; // If we hit a strip-restart index, then skip to the next index. if ((PrimitiveType == PrimitiveType.TriangleStrip) && (index1 < 0)) { index1 = indexData[indexOffset++]; } int index2 = indexData[indexOffset++]; int index3 = indexData[indexOffset++]; Vertex3D vertex1 = vertexData[index1]; Vertex3D vertex2 = vertexData[index2]; Vertex3D vertex3 = vertexData[index3]; DX.Vector4.Subtract(ref vertex2.Position, ref vertex1.Position, out DX.Vector4 deltaPos1); DX.Vector4.Subtract(ref vertex3.Position, ref vertex1.Position, out DX.Vector4 deltaPos2); DX.Vector2.Subtract(ref vertex2.UV, ref vertex1.UV, out DX.Vector2 deltaUV1); DX.Vector2.Subtract(ref vertex3.UV, ref vertex1.UV, out DX.Vector2 deltaUV2); float denom = ((deltaUV1.X * deltaUV2.Y) - (deltaUV1.Y * deltaUV2.X)); float r = 0.0f; if (!denom.EqualsEpsilon(0)) { r = 1.0f / denom; } var tangent = new DX.Vector3(((deltaUV2.Y * deltaPos1.X) - (deltaUV1.Y * deltaPos2.X)) * r, ((deltaUV2.Y * deltaPos1.Y) - (deltaUV1.Y * deltaPos2.Y)) * r, ((deltaUV2.Y * deltaPos1.Z) - (deltaUV1.Y * deltaPos2.Z)) * r); var biTangent = new DX.Vector3(((deltaUV1.X * deltaPos2.X) - (deltaUV2.X * deltaPos1.X)) * r, ((deltaUV1.X * deltaPos2.Y) - (deltaUV2.X * deltaPos1.Y)) * r, ((deltaUV1.X * deltaPos2.Z) - (deltaUV2.X * deltaPos1.Z)) * r); DX.Vector3.Add(ref tanData[index1], ref tangent, out tanData[index1]); DX.Vector3.Add(ref tanData[index2], ref tangent, out tanData[index2]); DX.Vector3.Add(ref tanData[index3], ref tangent, out tanData[index3]); DX.Vector3.Add(ref biTanData[index1], ref biTangent, out biTanData[index1]); DX.Vector3.Add(ref biTanData[index2], ref biTangent, out biTanData[index2]); DX.Vector3.Add(ref biTanData[index3], ref biTangent, out biTanData[index3]); } for (int i = 0; i < VertexCount; ++i) { Vertex3D vertex = vertexData[i]; DX.Vector3.Dot(ref vertex.Normal, ref tanData[i], out float dot); DX.Vector3.Multiply(ref vertex.Normal, dot, out DX.Vector3 tangent); DX.Vector3.Subtract(ref tanData[i], ref tangent, out tangent); DX.Vector3.Normalize(ref tangent, out tangent); DX.Vector3.Cross(ref vertex.Normal, ref tanData[i], out DX.Vector3 cross); DX.Vector3.Dot(ref cross, ref biTanData[i], out dot); vertexData[i] = new Vertex3D { Position = vertex.Position, Normal = vertex.Normal, UV = vertex.UV, Tangent = new DX.Vector4(tangent, dot < 0.0f ? -1.0f : 1.0f) }; } }
/// <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 create a vertex buffer and its binding. /// </summary> /// <typeparam name="T">The type of data representing a vertex, must be an unmanaged value type.</typeparam> /// <param name="graphics">The graphics interface that will create the buffer.</param> /// <param name="info">Information about the buffer to create.</param> /// <param name="initialData">[Optional] An initial set of vertex data to send to the buffer.</param> /// <param name="bindingIndex">[Optional] The index, in vertices, inside the buffer where binding is to begin.</param> /// <returns>A new <see cref="GorgonVertexBufferBinding"/>.</returns> /// <exception cref="ArgumentNullException">Thrown when the <paramref name="graphics"/>, or the <paramref name="info"/> parameter is <b>null</b>.</exception> /// <remarks> /// <para> /// Use this to quickly create a vertex buffer and its binding based on a known vertex data type. /// </para> /// <para> /// Be aware that the <see cref="VertexBuffer"/> created by this method must be disposed manually after it is no longer of any use. /// </para> /// </remarks> /// <seealso cref="GorgonVertexBuffer"/> public static GorgonVertexBufferBinding CreateVertexBuffer <T>(GorgonGraphics graphics, IGorgonVertexBufferInfo info, GorgonNativeBuffer <T> initialData = null, int bindingIndex = 0) where T : unmanaged { if (graphics == null) { throw new ArgumentNullException(nameof(graphics)); } if (info == null) { throw new ArgumentNullException(nameof(info)); } var buffer = new GorgonVertexBuffer(graphics, info, initialData?.Cast <byte>()); int vertexSize = Unsafe.SizeOf <T>(); return(new GorgonVertexBufferBinding(buffer, vertexSize, bindingIndex * vertexSize)); }
/// <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> /// Function to build the Icosphere. /// </summary> /// <param name="graphics">Graphics interface to use.</param> /// <param name="radius">Radius of the sphere.</param> /// <param name="tesselation">Tessellation factor for the sphere.</param> /// <param name="textureCoordinates">Texture coordinate offset and scale.</param> private void BuildSphere(GorgonGraphics graphics, float radius, int tesselation, DX.RectangleF textureCoordinates) { GetBaseVertices(); List <int[]> indices = GetBaseIndices(); for (int i = 0; i < tesselation; ++i) { var subIndices = new List <int[]>(); foreach (int[] index in indices) { int index0 = GetMiddlePoint(index[0], index[1]); int index1 = GetMiddlePoint(index[1], index[2]); int index2 = GetMiddlePoint(index[2], index[0]); subIndices.Add(new [] { index[0], index0, index2 }); subIndices.Add(new[] { index[1], index1, index0 }); subIndices.Add(new[] { index[2], index2, index1 }); subIndices.Add(new[] { index0, index1, index2 }); } indices = subIndices; _cachedSplits.Clear(); } // Perform texture coordinate calculations and vertex/normal transformations. const float piRecip = 1.0f / (float)System.Math.PI; const float pi2Recip = 1.0f / (2.0f * (float)System.Math.PI); // Final list. var vertexList = new List <Vertex3D>(); var indexList = new List <int>(); foreach (DX.Vector3 vector in _vertices) { DX.Vector3 position = vector; DX.Vector3 normal = position; DX.Vector2 uv = DX.Vector2.Zero; uv.X = ((0.5f - (position.X.ATan(position.Z) * pi2Recip)) * textureCoordinates.Width) + textureCoordinates.X; uv.Y = ((0.5f - (position.Y.ASin() * piRecip)) * textureCoordinates.Height) + textureCoordinates.Y; DX.Vector3.Multiply(ref position, radius, out position); DX.Vector3.TransformCoordinate(ref position, ref _orientation, out position); DX.Vector3.TransformCoordinate(ref normal, ref _orientation, out normal); normal.Normalize(); vertexList.Add(new Vertex3D { Position = new DX.Vector4(position, 1.0f), Normal = normal, UV = uv }); } foreach (int[] index in indices) { for (int j = 0; j < 3; ++j) { indexList.Add(index[j]); } } FixSeam(vertexList, indexList); using (var vertexData = GorgonNativeBuffer <Vertex3D> .Pin(vertexList.ToArray())) using (var indexData = GorgonNativeBuffer <int> .Pin(indexList.ToArray())) { VertexCount = vertexList.Count; IndexCount = indexList.Count; TriangleCount = IndexCount / 3; CalculateTangents(vertexData, indexData); VertexBuffer = new GorgonVertexBuffer(graphics, new GorgonVertexBufferInfo("IcoSphereVertexBuffer") { SizeInBytes = vertexData.SizeInBytes, Usage = ResourceUsage.Immutable }, vertexData.Cast <byte>()); IndexBuffer = new GorgonIndexBuffer(graphics, new GorgonIndexBufferInfo { Usage = ResourceUsage.Immutable, Use16BitIndices = false, IndexCount = IndexCount }, indexData); } }
/// <summary> /// Function to create the vertex data for the sphere. /// </summary> /// <param name="vertexData">Pointer to the buffer that will hold the vertex data.</param> /// <param name="indexData">Pointer to the buffer that will hold the index data.</param> /// <param name="radius">Radius of the sphere.</param> /// <param name="textureCoordinates">Texture coordinates for the sphere.</param> /// <param name="ringCount">Number of rings in the sphere.</param> /// <param name="segmentCount">Number of segments in the sphere.</param> private void GetVertices(GorgonNativeBuffer <Vertex3D> vertexData, GorgonNativeBuffer <int> indexData, float radius, RectangleF textureCoordinates, int ringCount, int segmentCount) { int index = 0; // Current index. int vertexOffset = 0; int indexOffset = 0; float deltaRingAngle = ((float)System.Math.PI) / ringCount; float deltaSegAngle = (((float)System.Math.PI) * 2.0f) / segmentCount; Radius = radius; // Build our sphere. for (int ring = 0; ring <= ringCount; ring++) { float ringAngle = ring * deltaRingAngle; radius = ringAngle.Sin() * 0.5f * Radius; float radiusY = ringAngle.Cos() * Radius * 0.5f; for (int segment = 0; segment <= segmentCount; segment++) { var textureDelta = new DX.Vector2(1.0f - (segment / (float)segmentCount), ring / (float)ringCount); float segmentAngle = deltaSegAngle * segment; var position = new DX.Vector3(radius * segmentAngle.Sin(), radiusY, radius * segmentAngle.Cos()); DX.Vector3.Multiply(ref position, 2.0f, out DX.Vector3 normal); DX.Vector3.TransformCoordinate(ref position, ref _orientation, out position); DX.Vector3.TransformCoordinate(ref normal, ref _orientation, out normal); normal.Normalize(); // Create the vertex. textureDelta.X *= textureCoordinates.Width; textureDelta.Y *= textureCoordinates.Height; textureDelta.X += textureCoordinates.X; textureDelta.Y += textureCoordinates.Y; vertexData[vertexOffset++] = new Vertex3D { Position = new DX.Vector4(position, 1.0f), UV = textureDelta, Normal = normal }; // Add the indices and skip the last ring. if (ring == ringCount) { continue; } indexData[indexOffset++] = (index + segmentCount + 1); indexData[indexOffset++] = index; indexData[indexOffset++] = (index + segmentCount); indexData[indexOffset++] = (index + segmentCount + 1); indexData[indexOffset++] = (index + 1); indexData[indexOffset++] = index; index++; } } }
/// <summary> /// Function to convert the image data into a premultiplied format. /// </summary> /// <param name="baseImage">The image to convert.</param> /// <returns>A <see cref="IGorgonImage"/> containing the image data with the premultiplied alpha pixel data.</returns> /// <exception cref="ArgumentNullException">Thrown when the <paramref name="baseImage"/> is <b>null</b>.</exception> /// <exception cref="ArgumentException">Thrown when image format is compressed.</exception> /// <remarks> /// <para> /// Use this to convert an image to a premultiplied format. This takes each Red, Green and Blue element and multiplies them by the Alpha element. /// </para> /// <para> /// If the image does not contain alpha then the method will return right away and no alterations to the image will be performed. /// </para> /// </remarks> public static IGorgonImage ConvertToPremultipliedAlpha(this IGorgonImage baseImage) { IGorgonImage newImage = null; GorgonNativeBuffer <byte> imageData = null; if (baseImage == null) { throw new ArgumentNullException(nameof(baseImage)); } if (!baseImage.FormatInfo.HasAlpha) { return(baseImage); } if (baseImage.FormatInfo.IsCompressed) { throw new ArgumentException(string.Format(Resources.GORIMG_ERR_FORMAT_NOT_SUPPORTED, baseImage.Format), nameof(baseImage)); } try { var cloneImageInfo = new GorgonImageInfo(baseImage) { HasPreMultipliedAlpha = true }; imageData = new GorgonNativeBuffer <byte>(baseImage.ImageData.Length); baseImage.ImageData.CopyTo(imageData); unsafe { newImage = new GorgonImage(cloneImageInfo, new GorgonReadOnlyPointer((void *)imageData, imageData.SizeInBytes)); int arrayOrDepth = newImage.ImageType == ImageType.Image3D ? newImage.Depth : newImage.ArrayCount; for (int mip = 0; mip < newImage.MipCount; ++mip) { for (int i = 0; i < arrayOrDepth; ++i) { IGorgonImageBuffer buffer = newImage.Buffers[mip, i]; byte *ptr = (byte *)buffer.Data; int rowPitch = buffer.PitchInformation.RowPitch; for (int y = 0; y < buffer.Height; ++y) { ImageUtilities.SetPremultipliedScanline(ptr, rowPitch, ptr, rowPitch, buffer.Format); ptr += rowPitch; } } } } newImage.CopyTo(baseImage); return(baseImage); } finally { imageData?.Dispose(); newImage?.Dispose(); } }
/// <summary> /// Function to generate a new mip map chain. /// </summary> /// <param name="baseImage">The image which will have its mip map chain updated.</param> /// <param name="mipCount">The number of mip map levels.</param> /// <param name="filter">[Optional] The filter to apply when copying the data from one mip level to another.</param> /// <returns>A <see cref="IGorgonImage"/> containing the updated mip map data.</returns> /// <exception cref="ArgumentNullException">Thrown when the <paramref name="baseImage"/> parameter is <b>null</b>.</exception> /// <remarks> /// <para> /// This method will generate a new mip map chain for the <paramref name="mipCount"/>. If the current number of mip maps is not the same as the requested number, then the image buffer will be /// adjusted to use the requested number of mip maps. If 0 is passed to <paramref name="mipCount"/>, then a full mip map chain is generated. /// </para> /// <para> /// Note that the <paramref name="mipCount"/> may not be honored depending on the current width, height, and depth of the image. Check the width, height and/or depth property on the returned /// <see cref="IGorgonImage"/> to determine how many mip levels were actually generated. /// </para> /// </remarks> public static IGorgonImage GenerateMipMaps(this IGorgonImage baseImage, int mipCount, ImageFilter filter = ImageFilter.Point) { if (baseImage == null) { throw new ArgumentNullException(nameof(baseImage)); } int maxMips = GorgonImage.CalculateMaxMipCount(baseImage); // If we specify 0, then generate a full chain. if ((mipCount <= 0) || (mipCount > maxMips)) { mipCount = maxMips; } // If we don't have any mip levels, then return the image as-is. if (mipCount < 2) { return(baseImage); } var destSettings = new GorgonImageInfo(baseImage) { MipCount = mipCount }; var newImage = new GorgonImage(destSettings); var wic = new WicUtilities(); try { // Copy the top mip level from the source image to the dest image. for (int array = 0; array < baseImage.ArrayCount; ++array) { GorgonNativeBuffer <byte> buffer = newImage.Buffers[0, array].Data; int size = buffer.SizeInBytes; baseImage.Buffers[0, array].Data.CopyTo(buffer, count: size); } // If we have 4 bits per channel, then we need to convert to 8 bit per channel to make WIC happy. if (baseImage.Format == BufferFormat.B4G4R4A4_UNorm) { newImage.ConvertToFormat(BufferFormat.R8G8B8A8_UNorm); } wic.GenerateMipImages(newImage, filter); // Convert back if we asked for 4 bit per channel. if (baseImage.Format == BufferFormat.B4G4R4A4_UNorm) { newImage.ConvertToFormat(BufferFormat.B4G4R4A4_UNorm); } newImage.CopyTo(baseImage); return(baseImage); } finally { newImage.Dispose(); wic.Dispose(); } }