Beispiel #1
0
        /// <summary>
        /// Constructs (via Linq expressions) a delegate for setting the default uniform block of a given program from an equivalent .NET struct.
        /// Struct fields and uniforms are matched by name - which must match exactly.
        /// </summary>
        /// <typeparam name="T">The type of .NET struct that is the equivalent of the default uniform block.</typeparam>
        /// <param name="programId">The name of the Open GL program.</param>
        /// <returns>A delegate to invoke to set the default uniform block.</returns>
        /// <remarks>
        /// NB: Much better than reflection every time, but probably less efficient than it could be due to clunky assembly of expressions.
        /// A prime candidate for replacement by source generation.
        /// </remarks>
        public static Action <T> MakeDefaultUniformBlockSetter <T>(int programId)
            where T : struct
        {
            var inputParam = Expression.Parameter(typeof(T));
            var setters    = new List <Expression>();

            var publicFields = typeof(T).GetFields();

            GlDebug.WriteLine($"{typeof(T).FullName} public fields that will be mapped to uniforms by name: {string.Join(", ", publicFields.Select(f => f.Name))}");
            foreach (var field in publicFields)
            {
                var uniformLocation = GL.GetUniformLocation(programId, field.Name);
                if (uniformLocation == -1)
                {
                    throw new ArgumentException($"Uniform struct contains field '{field.Name}', which does not exist as a uniform in this program.");
                }

                if (!DefaultBlockUniformSettersByType.TryGetValue(field.FieldType, out var uniformSetter))
                {
                    throw new ArgumentException($"Uniform struct contains field of unsupported type {field.FieldType}", nameof(T));
                }

                setters.Add(Expression.Invoke(
                                uniformSetter,
                                Expression.Constant(uniformLocation),
                                Expression.Field(inputParam, field)));
            }

            // TODO: Check for any uniforms that we missed, and either throw or debug warn..
            ////GL.GetProgram(programId, GetProgramParameterName.ActiveUniforms, ..)

            return(Expression.Lambda <Action <T> >(Expression.Block(setters), inputParam).Compile());
        }
Beispiel #2
0
 /// <inheritdoc />
 public void Copy(int readIndex, int writeIndex, int count)
 {
     GL.CopyNamedBufferSubData(
         readBuffer: Id,
         writeBuffer: Id,
         readOffset: new IntPtr(readIndex * ElementSize),
         writeOffset: new IntPtr(writeIndex * ElementSize),
         size: count * ElementSize);
     GlDebug.ThrowIfGlError("copying buffer sub-data");
 }
Beispiel #3
0
        /// <summary>
        /// Initializes a new instance of the <see cref="GlVertexBufferObject{T}"/> class. SIDE EFFECT: New buffer will be bound to the given target.
        /// </summary>
        /// <param name="target">OpenGL buffer target specification.</param>
        /// <param name="usage">OpenGL buffer usage specification.</param>
        /// <param name="elementCapacity">The maximum number of elements to be stored in the buffer.</param>
        /// <param name="elementData">The data to populate the buffer with.</param>
        public GlVertexBufferObject(BufferTarget target, BufferUsageHint usage, int elementCapacity, T[] elementData)
        {
            this.Capacity = elementCapacity;

            this.Id = GL.GenBuffer();
            GlDebug.ThrowIfGlError("creating buffer");
            GL.BindBuffer(target, this.Id); // NB: Side effect - leaves this buffer bound.
            GlDebug.ThrowIfGlError("binding buffer");
            GL.BufferData(target, ElementSize * elementCapacity, elementData, usage);
            GlDebug.ThrowIfGlError("creating buffer data store");
        }
Beispiel #4
0
            public static GlUniformBlockBinding Bind(int programId, string blockName)
            {
                GlUniformBlockBinding binding;

                // Get the block index
                var uniformBlockIndex = GL.GetUniformBlockIndex(programId, blockName);

                GlDebug.ThrowIfGlError("getting uniform block index");
                if (uniformBlockIndex == (int)All.InvalidIndex)
                {
                    throw new ArgumentException($"Uniform block '{blockName}' not found.", nameof(blockName));
                }

                // Check if this block already has an assigned binding point.
                GL.GetActiveUniformBlock(programId, uniformBlockIndex, ActiveUniformBlockParameter.UniformBlockBinding, out int bindingPointIndex);
                GlDebug.ThrowIfGlError("getting uniform block binding");

                if (bindingPointIndex != 0)
                {
                    // Block already has a binding (might be specified in the shader), so..

                    // If we've already created a binding ref for this block index, use that.
                    // Otherwise create a new one.
                    if (!BindingsByBindingPoint.TryGetValue(bindingPointIndex, out binding))
                    {
                        binding = new GlUniformBlockBinding(bindingPointIndex, blockName);
                    }
                }
                else
                {
                    // Block doesn't already have a binding, so..

                    // If we've already created a binding point for this block name, use that.
                    // Otherwise assign a brand new one.
                    if (!BindingsByBlockName.TryGetValue(blockName, out binding))
                    {
                        // TODO: Not right. should also skip bindings already in use in OpenGL (because theyre specified in shaders)
                        // that we aren't aware of yet. might be better to not maintain a record ourselves and always just query opengl
                        int firstUnusedBindingPoint = 1;
                        while (BindingsByBindingPoint.ContainsKey(firstUnusedBindingPoint))
                        {
                            firstUnusedBindingPoint++;
                        }

                        binding = new GlUniformBlockBinding(firstUnusedBindingPoint, blockName);
                    }

                    // Bind this block to the binding point
                    GL.UniformBlockBinding(programId, uniformBlockIndex, binding.BindingPoint);
                    GlDebug.ThrowIfGlError("setting uniform block binding");
                }

                return(binding);
            }
Beispiel #5
0
            private void Delete(bool finalizing)
            {
                if (!finalizing)
                {
                    GC.SuppressFinalize(this);
                }

                isDeleted = true;
                GL.DeleteBuffer(Id);
                GlDebug.ThrowIfGlError("deleting buffer");
            }
        /// <summary>
        /// Initializes a new instance of the <see cref="GlUniformBufferObject{T}"/> class. SIDE EFFECT: New buffer will be bound to the UniformBuffer target.
        /// </summary>
        /// <param name="programId">ID of a linked program that uses the block that this buffer stores the data for.</param>
        /// <param name="blockName">The name of the block in the program that this buffer stores the data for.</param>
        /// <param name="usage">OpenGL buffer usage specification.</param>
        /// <param name="elementCapacity">The maximum number of elements to be stored in the buffer.</param>
        /// <param name="elementData">The data to populate the buffer with.</param>
        /// <remarks>
        /// TODO: I don't like the fact that we pass program ID and block name here (since this could be used by multiple programs) - would rather pass metadata - GlUniformBlockInfo or something.
        /// </remarks>
        internal GlUniformBufferObject(int programId, string blockName, BufferUsageHint usage, int elementCapacity, T[] elementData)
        {
            this.setter   = GlMarshal.MakeBufferObjectUniformSetter <T>(programId, blockName);
            this.Capacity = elementCapacity;

            this.bindingRef = new GlUniformBlockBindingRef(programId, blockName);
            GL.BindBuffer(BufferTarget.UniformBuffer, this.Id); // NB: Side effect - leaves this buffer bound.
            GlDebug.ThrowIfGlError("Binding uniform buffer");

            var blockIndex = GL.GetUniformBlockIndex(programId, blockName);

            GlDebug.ThrowIfGlError("Getting uniform block index");
            GL.GetActiveUniformBlock(programId, blockIndex, ActiveUniformBlockParameter.UniformBlockDataSize, out elementSize);
            GlDebug.ThrowIfGlError("Getting uniform block size");
            GL.BufferData(BufferTarget.UniformBuffer, elementSize * elementCapacity, elementData, usage);
            GlDebug.ThrowIfGlError("Creating uniform buffer data store");

            // TODO: Not always what we want.. Replace by Bind(int index) in UBO class..?
            GL.BindBufferBase(BufferRangeTarget.UniformBuffer, bindingRef.BindingPoint, bindingRef.BufferRef.Id);
            GlDebug.ThrowIfGlError("setting uniform buffer binding");
        }
Beispiel #7
0
        /// <inheritdoc />
        public T this[int index]
        {
            get
            {
                T data = default;
                GL.GetNamedBufferSubData(
                    buffer: Id,
                    offset: new IntPtr(index * ElementSize),
                    size: ElementSize,
                    data: ref data);
                GlDebug.ThrowIfGlError("getting buffer sub-data");
                return(data);
            }

            set
            {
                GL.NamedBufferSubData(
                    buffer: Id,
                    offset: new IntPtr(index * ElementSize),
                    size: ElementSize,
                    data: ref value);
                GlDebug.ThrowIfGlError("setting buffer sub-data");
            }
        }
Beispiel #8
0
 public GlBuffer()
 {
     this.id = GL.GenBuffer();
     GlDebug.ThrowIfGlError("generating buffer");
 }
Beispiel #9
0
        /// <summary>
        /// Constructs (via Linq expressions) a delegate for setting a uniform block of a given program from an equivalent .NET struct.
        /// Struct fields and uniforms are matched by name - which must match exactly.
        /// </summary>
        /// <typeparam name="T">The type of .NET struct that is the equivalent of the uniform block.</typeparam>
        /// <param name="programId">The name of the Open GL program.</param>
        /// <param name="blockName">The name of the uniform block.</param>
        /// <returns>A delegate to invoke to set the uniform block. In order, the parameters are buffer ID, index within the buffer (in multiples of the block - NOT byte offset), and the value to set.</returns>
        public static Action <int, int, T> MakeBufferObjectUniformSetter <T>(int programId, string blockName)
            where T : struct
        {
            var publicFields = typeof(T).GetFields();

            GlDebug.WriteLine($"{typeof(T).FullName} public fields that will be mapped to uniforms by name: {string.Join(", ", publicFields.Select(f => f.Name))}");

            var uniformNames   = publicFields.Select(fi => $"{blockName}.{fi.Name}").ToArray();
            var uniformIndices = new int[uniformNames.Length];

            GL.GetUniformIndices(programId, uniformNames.Length, uniformNames, uniformIndices);
            GlDebug.ThrowIfGlError("getting uniform indices");
            if (uniformIndices.Any(i => i == (int)All.InvalidIndex))
            {
                // TODO: Actually say which ones weren't found
                throw new ArgumentException($"At least one of {string.Join(", ", publicFields.Select(fi => $"{blockName}.{fi.Name}"))} could not be found in the program");
            }

            //// TODO: check for any extras?

            var uniformOffsets = new int[uniformNames.Length];

            GL.GetActiveUniforms(programId, uniformIndices.Length, uniformIndices, ActiveUniformParameter.UniformOffset, uniformOffsets);
            var arrayStrides = new int[uniformNames.Length];

            GL.GetActiveUniforms(programId, uniformIndices.Length, uniformIndices, ActiveUniformParameter.UniformArrayStride, arrayStrides);
            var matrixStrides = new int[uniformNames.Length];

            GL.GetActiveUniforms(programId, uniformIndices.Length, uniformIndices, ActiveUniformParameter.UniformMatrixStride, matrixStrides);

            var pointerParam     = Expression.Parameter(typeof(IntPtr));
            var blockParam       = Expression.Parameter(typeof(T));
            var blockExpressions = new List <Expression>();

            for (int i = 0; i < publicFields.Length; i++)
            {
                //// TODO: the below is the default for non-array, non-matrix types (but should work when stride is zero?)
                //// if an array or matrix type, need to copy it in pieces, paying attention to strides.. How to tell..
                var structureToPtrMethod = typeof(Marshal)
                                           .GetMethod(nameof(Marshal.StructureToPtr), 1, new[] { Type.MakeGenericMethodParameter(0), typeof(IntPtr), typeof(bool) })
                                           .MakeGenericMethod(publicFields[i].FieldType);

                blockExpressions.Add(Expression.Call(
                                         structureToPtrMethod,
                                         Expression.Field(blockParam, publicFields[i]),
                                         Expression.Add(pointerParam, Expression.Constant(uniformOffsets[i])),
                                         Expression.Constant(false)));
            }

            var setFields = Expression.Lambda <Action <IntPtr, T> >(Expression.Block(blockExpressions), pointerParam, blockParam).Compile();

            void Set(int bufferId, int index, T param)
            {
                GL.BindBuffer(BufferTarget.UniformBuffer, bufferId);
                GlDebug.ThrowIfGlError("binding buffer");
                var bufferPtr = GL.MapBuffer(BufferTarget.UniformBuffer, BufferAccess.WriteOnly);

                GlDebug.ThrowIfGlError("mapping buffer");
                setFields(bufferPtr, param);
                GL.UnmapBuffer(BufferTarget.UniformBuffer);
                GlDebug.ThrowIfGlError("unmapping buffer");
            }

            return(Set);
        }