/// <summary> /// Creates a <see cref="VertexAttribDescription"/> where the format of the data declared /// in the shader isn't the same as the format the data will be read as. /// </summary> /// <param name="attribType">The type of the attribute declared in the shader.</param> /// <param name="normalized">Whether the vertex data should be normalized before being loaded into the shader.</param> /// <param name="dataBaseType">The base type in which the data will be read from the buffer.</param> /// <param name="attribDivisor">The divisor that defines how reading this attribute advances on instanced rendering.</param> public VertexAttribDescription(AttributeType attribType, bool normalized, AttributeBaseType dataBaseType, uint attribDivisor = 0) { ValidateAttribDivisor(attribDivisor); if (normalized) { if (!TrippyUtils.IsVertexAttribIntegerType(dataBaseType)) { throw new ArgumentException("For normalized vertex attributes, the dataBaseType must be an integer", nameof(dataBaseType)); } if (!(TrippyUtils.IsVertexAttribFloatType(attribType) || TrippyUtils.IsVertexAttribDoubleType(attribType))) { throw new ArgumentException("For normalized vertex attributes, the attribType must be a float or a double", nameof(attribType)); } } Normalized = normalized; AttribDivisor = attribDivisor; AttribBaseType = dataBaseType; AttribType = attribType; Size = TrippyUtils.GetVertexAttribTypeSize(attribType); AttribIndicesUseCount = TrippyUtils.GetVertexAttribTypeIndexCount(attribType); SizeInBytes = TrippyUtils.GetVertexAttribSizeInBytes(dataBaseType) * (uint)Size * AttribIndicesUseCount; }
/// <summary> /// Attaches a <see cref="RenderbufferObject"/> to this <see cref="FramebufferObject"/> in a specified attachment point. /// </summary> /// <param name="renderbuffer">The <see cref="RenderbufferObject"/> to attach.</param> /// <param name="attachmentPoint">The attachment point to attach the <see cref="RenderbufferObject"/> to.</param> public void Attach(RenderbufferObject renderbuffer, FramebufferAttachmentPoint attachmentPoint) { if (renderbuffer == null) { throw new ArgumentNullException(nameof(renderbuffer)); } ValidateAttachmentTypeExists(attachmentPoint); ValidateAttachmentTypeNotUsed(attachmentPoint); if (attachmentPoint == FramebufferAttachmentPoint.Depth && !renderbuffer.IsDepthOnly) { throw new InvalidFramebufferAttachmentException("When attaching a renderbuffer to a depth attachment point, the renderbuffer's format must be depth-only"); } if (attachmentPoint == FramebufferAttachmentPoint.DepthStencil && !renderbuffer.IsDepthStencil) { throw new InvalidFramebufferAttachmentException("When attaching a renderbuffer to a depth-stencil attachment point, the renderbuffer's format must be depth-stencil"); } if (attachmentPoint == FramebufferAttachmentPoint.Stencil && !renderbuffer.IsStencilOnly) { throw new InvalidFramebufferAttachmentException("When attaching a renderbuffer to a stencil attachment point, the renderbuffer's format must be stencil-only"); } if (TrippyUtils.IsFramebufferAttachmentPointColor(attachmentPoint) && !renderbuffer.IsColorRenderableFormat) { throw new InvalidFramebufferAttachmentException("When attaching a renderbuffer to a color attachment point, the renderbuffer's format must be color-renderable"); } GraphicsDevice.Framebuffer = this; GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, (FramebufferAttachment)attachmentPoint, RenderbufferTarget.Renderbuffer, renderbuffer.Handle); renderbufferAttachments.Add(new FramebufferRenderbufferAttachment(renderbuffer, attachmentPoint)); }
/// <summary> /// Creates a <see cref="Framebuffer2D"/> with the given width, height, and other optional parameters. /// </summary> /// <param name="graphicsDevice">The <see cref="GraphicsDevice"/> this <see cref="Framebuffer2D"/> will use.</param> /// <param name="width">The width of the <see cref="Framebuffer2D"/>'s image.</param> /// <param name="height">The height of the <see cref="Framebuffer2D"/>'s image.</param> /// <param name="depthStencilFormat">The depth-stencil format for an optional renderbuffer attachment.</param> /// <param name="samples">The amount of samples for the <see cref="Framebuffer2D"/>'s image.</param> /// <param name="imageFormat">The format of the <see cref="Framebuffer2D"/>'s image.</param> /// <param name="useDepthStencilTexture">Whether to use a texture for the depth-stencil buffer instead of a renderbuffer.</param> public Framebuffer2D(GraphicsDevice graphicsDevice, uint width, uint height, DepthStencilFormat depthStencilFormat, uint samples = 0, TextureImageFormat imageFormat = TextureImageFormat.Color4b, bool useDepthStencilTexture = false) { Framebuffer = new FramebufferObject(graphicsDevice); Texture = new Texture2D(graphicsDevice, width, height, false, samples, imageFormat); if (depthStencilFormat != DepthStencilFormat.None) { if (useDepthStencilTexture) { TextureImageFormat dsFormat = TrippyUtils.DepthStencilFormatToTextureFormat(depthStencilFormat); Texture2D dsTexture = new Texture2D(graphicsDevice, width, height, false, samples, dsFormat); Framebuffer.Attach(dsTexture, TrippyUtils.GetCorrespondingTextureFramebufferAttachmentPoint(dsFormat)); } else { RenderbufferObject rbo = new RenderbufferObject(graphicsDevice, width, height, (RenderbufferFormat)depthStencilFormat, samples); Framebuffer.Attach(rbo, TrippyUtils.GetCorrespondingRenderbufferFramebufferAttachmentPoint(rbo.Format)); } } Framebuffer.Attach(Texture, FramebufferAttachmentPoint.Color0); Framebuffer.UpdateFramebufferData(); }
/// <summary> /// Creates a <see cref="VertexAttribDescription"/> that specifies padding for the amount /// of bytes used by a specified <see cref="AttributeType"/>. /// </summary> /// <remarks> /// Padding indicators ignore padding based on type that occurs when using compensation /// for struct padding (which is the default behavior in <see cref="VertexArray"/>). /// </remarks> public static VertexAttribDescription CreatePadding(AttributeType attribType) { TrippyUtils.GetVertexAttribTypeData(attribType, out uint indexUseCount, out int size, out AttributeBaseType baseType); uint typeSize = TrippyUtils.GetVertexAttribSizeInBytes(baseType); return(new VertexAttribDescription(typeSize * (uint)size * indexUseCount)); }
public unsafe void CallGlVertexAttribPointer(GL gl) { source.BufferSubset.Buffer.GraphicsDevice.BindBuffer(source.BufferSubset); uint offs = offset + source.BufferSubset.StorageOffsetInBytes; uint stride = source.BufferSubset.ElementSize; for (uint i = 0; i < source.AttribDescription.AttribIndicesUseCount; i++) { if (!source.AttribDescription.Normalized && source.AttribDescription.AttribBaseType == VertexAttribPointerType.Double) { gl.VertexAttribLPointer(index + i, source.AttribDescription.Size, VertexAttribPointerType.Double, stride, (void *)offs); } else if (!source.AttribDescription.Normalized && TrippyUtils.IsVertexAttribIntegerType(source.AttribDescription.AttribType)) { gl.VertexAttribIPointer(index + i, source.AttribDescription.Size, source.AttribDescription.AttribBaseType, stride, (void *)offs); } else { gl.VertexAttribPointer(index + i, source.AttribDescription.Size, source.AttribDescription.AttribBaseType, source.AttribDescription.Normalized, stride, (void *)offs); } gl.EnableVertexAttribArray(index + i); if (source.AttribDescription.AttribDivisor != 0) { gl.VertexAttribDivisor(index + i, source.AttribDescription.AttribDivisor); } offs += source.AttribDescription.SizeInBytes / source.AttribDescription.AttribIndicesUseCount; } }
internal ShaderUniform(ShaderProgram owner, int uniformLoc, string name, int size, UniformType type) { OwnerProgram = owner; UniformLocation = uniformLoc; Size = size; UniformType = type; // The name might come as array name for array uniforms. // We need to turn the name "arrayUniform[0]" into just "arrayUniform" int nameIndexOfThing = name.LastIndexOf('['); Name = nameIndexOfThing > 0 ? name.Substring(0, name.Length - nameIndexOfThing + 1) : name; IsSamplerType = TrippyUtils.IsUniformSamplerType(type); if (IsSamplerType) { textureValues = new Texture[size]; textureLastAppliedUnits = new int[size]; textureLastAppliedUnits.AsSpan().Fill(-1); } else { textureValues = null; textureLastAppliedUnits = null; } }
/// <summary> /// Creates a <see cref="VertexAttribDescription"/> where the format of the data declared /// in the shader is the same as present in the buffer and no conversion needs to be done. /// </summary> /// <param name="attribType">The type of attribute declared in the shader.</param> /// <param name="attribDivisor">The divisor that defines how reading this attribute advances on instanced rendering.</param> public VertexAttribDescription(AttributeType attribType, uint attribDivisor = 0) { ValidateAttribDivisor(attribDivisor); TrippyUtils.GetVertexAttribTypeData(attribType, out AttribIndicesUseCount, out Size, out AttribBaseType); SizeInBytes = TrippyUtils.GetVertexAttribSizeInBytes(AttribBaseType) * (uint)Size * AttribIndicesUseCount; AttribType = attribType; Normalized = false; AttribDivisor = attribDivisor; }
/// <summary> /// Attaches a <see cref="Texture"/> to this <see cref="FramebufferObject"/> in a specified attachment point. /// </summary> /// <param name="texture">The <see cref="Texture"/> to attach.</param> /// <param name="attachmentPoint">The attachment point to attach the <see cref="Texture"/> to.</param> public void Attach(Texture texture, FramebufferAttachmentPoint attachmentPoint) { if (texture == null) { throw new ArgumentNullException(nameof(texture)); } ValidateAttachmentTypeExists(attachmentPoint); ValidateAttachmentTypeNotUsed(attachmentPoint); if (attachmentPoint == FramebufferAttachmentPoint.Depth && !TrippyUtils.IsImageFormatDepthOnly(texture.ImageFormat)) { throw new InvalidFramebufferAttachmentException("When attaching a texture to a depth attachment point, the texture's format must be depth-only"); } if (attachmentPoint == FramebufferAttachmentPoint.DepthStencil && !TrippyUtils.IsImageFormatDepthStencil(texture.ImageFormat)) { throw new InvalidFramebufferAttachmentException("When attaching a texture to a depth-stencil attachment point, the texture's format must be depth-stencil"); } if (attachmentPoint == FramebufferAttachmentPoint.Stencil && !TrippyUtils.IsImageFormatStencilOnly(texture.ImageFormat)) { throw new InvalidFramebufferAttachmentException("When attaching a texture to a stencil attachment point, the texture's format must be stencil-only"); } if (TrippyUtils.IsFramebufferAttachmentPointColor(attachmentPoint) && !TrippyUtils.IsImageFormatColorRenderable(texture.ImageFormat)) { throw new InvalidFramebufferAttachmentException("When attaching a texture to a color attachment point, the texture's format must be color-renderable"); } GraphicsDevice.Framebuffer = this; if (texture is Texture1D) { GL.FramebufferTexture1D(FramebufferTarget.Framebuffer, (FramebufferAttachment)attachmentPoint, texture.TextureType, texture.Handle, 0); } else if (texture is Texture2D) { GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, (FramebufferAttachment)attachmentPoint, texture.TextureType, texture.Handle, 0); } else { throw new InvalidFramebufferAttachmentException("This texture type cannot be attached to a framebuffer"); } textureAttachments.Add(new FramebufferTextureAttachment(texture, attachmentPoint)); }
/// <summary> /// Creates a <see cref="Texture"/> with specified <see cref="TextureType"/> and <see cref="TextureImageFormat"/>. /// </summary> /// <param name="graphicsDevice">The <see cref="GraphicsDevice"/> this resource will use.</param> /// <param name="type">The type of texture (or texture target) the texture will be.</param> /// <param name="imageFormat">The type of image format this texture will store.</param> internal Texture(GraphicsDevice graphicsDevice, TextureType type, TextureImageFormat imageFormat) : base(graphicsDevice) { if (!Enum.IsDefined(typeof(TextureType), type)) { throw new FormatException("Invalid texture target"); } if (!Enum.IsDefined(typeof(TextureImageFormat), imageFormat)) { throw new FormatException("Invalid texture image format"); } TextureType = type; ImageFormat = imageFormat; TrippyUtils.GetTextureFormatEnums(imageFormat, out PixelInternalFormat, out PixelType, out PixelFormat); lastBindUnit = 0; IsMipmapped = false; isNotMipmappable = !TrippyUtils.IsTextureTypeMipmappable(type); Handle = GL.GenTexture(); }
private void EnsureAttribsValid(VertexAttribSource[] attribSources) { if (attribSources.Length == 0) { throw new ArgumentException("You can't create a " + nameof(VertexArray) + " with no attributes", nameof(attribSources)); } uint attribIndexCount = 0; for (int i = 0; i < attribSources.Length; i++) { attribIndexCount += attribSources[i].AttribDescription.AttribIndicesUseCount; if (attribSources[i].AttribDescription.AttribDivisor != 0) { if (!GraphicsDevice.IsVertexAttribDivisorAvailable) { throw new PlatformNotSupportedException("Vertex attribute divisors are notsupported on this system"); } } } if (!GraphicsDevice.IsDoublePrecisionVertexAttribsAvailable) { for (int i = 0; i < attribSources.Length; i++) { if (TrippyUtils.IsVertexAttribDoubleType(attribSources[i].AttribDescription.AttribType)) { throw new PlatformNotSupportedException("Double precition vertex attributes are not supported on this system"); } } } if (attribIndexCount > GraphicsDevice.MaxVertexAttribs) { throw new PlatformNotSupportedException("The current system doesn't support this many vertex attributes"); } }
/// <summary> /// Compiles shaders using the code from the different XShaderCode fields and creates a /// GL Program Object by linking them together. Then, queries the active attributes and /// ensures they match the provided ones in <see cref="specifiedAttribs"/>. /// </summary> /// <param name="graphicsDevice">The <see cref="GraphicsDevice"/> the <see cref="ShaderProgram"/> will use.</param> /// <param name="activeAttribs">The active attributes found by querying the linked program.</param> /// <param name="getLogs">Whether to get compilation and linking logs from the shaders and program.</param> /// <returns>The handle of the newly created GL Program Object.</returns> /// <exception cref="ArgumentNullException"/> /// <exception cref="InvalidOperationException"/> /// <exception cref="ShaderCompilationException"/> /// <exception cref="ProgramLinkException"/> internal uint CreateInternal(GraphicsDevice graphicsDevice, out ActiveVertexAttrib[] activeAttribs, out bool hasVs, out bool hasGs, out bool hasFs, bool getLogs = false) { VertexShaderLog = null; FragmentShaderLog = null; GeometryShaderLog = null; ProgramLog = null; if (graphicsDevice == null) { throw new ArgumentNullException(nameof(graphicsDevice)); } if (!HasVertexShader) { throw new InvalidOperationException("A vertex shader must be specified"); } if (!HasAttribsSpecified) { throw new InvalidOperationException("Vertex attributes must be specified"); } // glCreateShader returns non-zero values so we can set these as default no problem uint vsHandle = 0; uint gsHandle = 0; uint fsHandle = 0; uint programHandle = 0; bool success = false; // We encapsulate the logic in a try catch so whether there is an // exception or not, we glDeleteShader all the shader handles try { // We create the vertex shader, compile the code and check it's status vsHandle = graphicsDevice.GL.CreateShader(ShaderType.VertexShader); hasVs = true; graphicsDevice.GL.ShaderSource(vsHandle, VertexShaderCode); graphicsDevice.GL.CompileShader(vsHandle); graphicsDevice.GL.GetShader(vsHandle, ShaderParameterName.CompileStatus, out int compileStatus); if (getLogs || compileStatus == (int)GLEnum.False) { VertexShaderLog = graphicsDevice.GL.GetShaderInfoLog(vsHandle); if (compileStatus == (int)GLEnum.False) { throw new ShaderCompilationException(ShaderType.VertexShader, VertexShaderLog); } } if (HasGeometryShader) { // If we have code for it, we create the geometry shader, compile the code and check it's status gsHandle = graphicsDevice.GL.CreateShader(ShaderType.GeometryShader); hasGs = true; graphicsDevice.GL.ShaderSource(gsHandle, GeometryShaderCode); graphicsDevice.GL.CompileShader(gsHandle); graphicsDevice.GL.GetShader(gsHandle, ShaderParameterName.CompileStatus, out compileStatus); if (getLogs || compileStatus == (int)GLEnum.False) { GeometryShaderLog = graphicsDevice.GL.GetShaderInfoLog(gsHandle); if (compileStatus == (int)GLEnum.False) { throw new ShaderCompilationException(ShaderType.GeometryShader, GeometryShaderLog); } } } else { hasGs = false; } if (HasFragmentShader) { // If we have code for it, we create the fragment shader, compile the code and check it's status fsHandle = graphicsDevice.GL.CreateShader(ShaderType.FragmentShader); hasFs = true; graphicsDevice.GL.ShaderSource(fsHandle, FragmentShaderCode); graphicsDevice.GL.CompileShader(fsHandle); graphicsDevice.GL.GetShader(fsHandle, ShaderParameterName.CompileStatus, out compileStatus); if (getLogs || compileStatus == (int)GLEnum.False) { FragmentShaderLog = graphicsDevice.GL.GetShaderInfoLog(fsHandle); if (compileStatus == (int)GLEnum.False) { throw new ShaderCompilationException(ShaderType.FragmentShader, FragmentShaderLog); } } } else { hasFs = false; } // We create the gl program object programHandle = graphicsDevice.GL.CreateProgram(); // We loop through all the attributes declared for the shader and bind them all to the correct location uint attribIndex = 0; for (uint i = 0; i < specifiedAttribs.Length; i++) { // Some attributes use more than 1 location, those ones we bind only once. // A null or empty name means we skip that attrib because the shader doesn't use it. // We still, though, have to advance attribIndex. if (!string.IsNullOrWhiteSpace(specifiedAttribs[i].Name)) { graphicsDevice.GL.BindAttribLocation(programHandle, attribIndex, specifiedAttribs[i].Name); } attribIndex += TrippyUtils.GetVertexAttribTypeIndexCount(specifiedAttribs[i].AttribType); } // We attach all the shaders we have to the program graphicsDevice.GL.AttachShader(programHandle, vsHandle); if (gsHandle != 0) { graphicsDevice.GL.AttachShader(programHandle, gsHandle); } if (fsHandle != 0) { graphicsDevice.GL.AttachShader(programHandle, fsHandle); } // We link the program and check it's status graphicsDevice.GL.LinkProgram(programHandle); graphicsDevice.GL.GetProgram(programHandle, ProgramPropertyARB.LinkStatus, out int linkStatus); if (getLogs || linkStatus == (int)GLEnum.False) { ProgramLog = graphicsDevice.GL.GetProgramInfoLog(programHandle); if (linkStatus == (int)GLEnum.False) { throw new ProgramLinkException(graphicsDevice.GL.GetProgramInfoLog(programHandle)); } } // We detach (and later delete) the shaders. These aren't actually detached // nor deleted until the program using them is done with them graphicsDevice.GL.DetachShader(programHandle, vsHandle); if (gsHandle != 0) { graphicsDevice.GL.DetachShader(programHandle, gsHandle); } if (fsHandle != 0) { graphicsDevice.GL.DetachShader(programHandle, fsHandle); } // We query the vertex attributes that were actually found on the compiled shader program activeAttribs = CreateActiveAttribArray(graphicsDevice, programHandle); // We ensure the queried vertex attributes match the user-provided ones if (!DoVertexAttributesMatch(activeAttribs, specifiedAttribs)) { throw new InvalidOperationException("The specified vertex attributes don't match the ones declared in the shaders"); } // Done! success = true; return(programHandle); } catch { // If something went wrong, we're not returning a ShaderProgram, but we might have // created the GL Program Object depending on what failed, so let's delete that. graphicsDevice.GL.DeleteProgram(programHandle); throw; // We re-throw the exception. } finally { // glDeleteShader calls get ignored if the shader handle is 0 graphicsDevice.GL.DeleteShader(vsHandle); graphicsDevice.GL.DeleteShader(gsHandle); graphicsDevice.GL.DeleteShader(fsHandle); graphicsDevice.OnShaderCompiled(this, success); } }
/// <summary> /// Creates a <see cref="VertexAttribDescription"/> that specifies padding for an /// amount of bytes calculated based on the baseType and size parameters. /// </summary> /// <remarks> /// Padding indicators ignore padding based on type that occurs when using compensation /// for struct padding (which is the default behavior in <see cref="VertexArray"/>). /// </remarks> public static VertexAttribDescription CreatePadding(AttributeBaseType baseType, uint size) { return(new VertexAttribDescription(TrippyUtils.GetVertexAttribSizeInBytes(baseType) * size)); }
/// <summary> /// Updates the places where vertex data is read from for this <see cref="VertexArray"/>. /// Call this whenever you modify a buffer subset used by this <see cref="VertexArray"/>. /// </summary> /// <param name="compensateStructPadding">Whether to compensate for struct padding. Default is true.</param> /// <param name="paddingPackValue">The struct packing value for compensating for padding. Default is 4.</param> public void UpdateVertexAttributes(bool compensateStructPadding = true, uint paddingPackValue = 4) { // Makes all glVertexAttribPointer calls to specify the vertex attrib data on the VAO and enables the vertex attributes. // The parameters of glVertexAttribPointer are calculated based on the VertexAttribSource-s from AttribSources GraphicsDevice.VertexArray = this; AttribCallDesc[] calls = new AttribCallDesc[attribSources.Length]; uint attribIndex = 0; for (int i = 0; i < calls.Length; i++) { calls[i] = new AttribCallDesc { source = attribSources[i], index = attribIndex, originalIndex = i }; attribIndex += calls[i].source.AttribDescription.AttribIndicesUseCount; } // Sort by buffer object, so all sources that share BufferObject are grouped together. // This facilitates calculating the offset values, since we only need to work with one offset at a time // rather than save the offset of each buffer simultaneously. Array.Sort(calls); // The calls array is now sorted. First by buffer handle, secondly by attrib index // and lastly, if two items have the same buffer handle and attrib index, they are sorted // by the original index they had in the calls array. // This logic is implemented in AttribCallDesc.CompareTo() if (compensateStructPadding) { #region CalculateOffsetsWithPadding uint offset = 0; BufferObjectSubset prevSubset = null; // Setting this to null ensures the first for loop will enter the "different subset" if and initialize these variables VertexAttribPointerType currentBaseType = 0; for (int i = 0; i < calls.Length; i++) { if (calls[i].source.BufferSubset != prevSubset) { // it's a different buffer subset, so let's calculate the padding values as for a new, different struct offset = 0; prevSubset = calls[i].source.BufferSubset; currentBaseType = calls[i].source.AttribDescription.AttribBaseType; calls[i].offset = 0; } else if (currentBaseType != calls[i].source.AttribDescription.AttribBaseType) { // the base type has changed, let's ensure padding is applied to offset currentBaseType = calls[i].source.AttribDescription.AttribBaseType; if (!calls[i].source.IsPadding) { // We add the manual padding, unless it is padding added specifically by the user uint packval = Math.Min(TrippyUtils.GetVertexAttribSizeInBytes(currentBaseType), paddingPackValue); // offset should be aligned by the default packing value or the size of the base type offset = (offset + packval - 1) / packval * packval; // Make offset be greater or equal to offset and divisible by packval } } calls[i].offset = offset; offset += calls[i].source.AttribDescription.SizeInBytes; } #endregion } else { #region CalculateOffsetsWithoutPadding uint offset = 0; uint prevBufferHandle = 0; for (int i = 0; i < calls.Length; i++) { if (prevBufferHandle != calls[i].source.BufferSubset.BufferHandle) { prevBufferHandle = calls[i].source.BufferSubset.BufferHandle; offset = 0; } calls[i].offset = offset; offset += calls[i].source.AttribDescription.SizeInBytes; } #endregion } for (int i = 0; i < calls.Length; i++) { calls[i].CallGlVertexAttribPointer(GL); } GL.BindBuffer(BufferTargetARB.ElementArrayBuffer, IndexBuffer == null ? 0 : IndexBuffer.BufferHandle); }