/// <summary> /// Constructor /// </summary> /// <param name="name">Name of bitmap collection</param> /// <param name="rlist">Render list to draw into</param> /// <param name="bitmapsize">Bit map size</param> /// <param name="mipmaplevels">Mip map levels</param> /// <param name="textureformat">Texture format of bitmaps</param> /// <param name="cullface">True to cull face</param> /// <param name="depthtest">True to depth test</param> /// <param name="maxpergroup">Maximum number of bitmaps per group</param> /// <param name="yfixed">Set true to fix Y co-ord externally</param> public GLBitmaps(string name, GLRenderProgramSortedList rlist, Size bitmapsize, int mipmaplevels = 3, OpenTK.Graphics.OpenGL4.SizedInternalFormat textureformat = OpenTK.Graphics.OpenGL4.SizedInternalFormat.Rgba8, bool cullface = true, bool depthtest = true, int maxpergroup = int.MaxValue, bool yfixed = false) { this.name = name; this.context = GLStatics.GetContext(); int maxdepthpertexture = GL4Statics.GetMaxTextureDepth(); // limits the number of textures per 2darray int max = Math.Min(maxdepthpertexture, maxpergroup); //note RI uses a VertexArray to load the matrix in, so not limited by that (max size of uniform buffer) matrixbuffers = new GLSetOfMatrixBufferWithGenerations(items, max); matrixbuffers.AddedNewGroup += AddedNewGroup; // hook up call back to say i've made a group renderlist = rlist; this.bitmapsize = bitmapsize; shader = new GLShaderPipeline(new GLPLVertexShaderMatrixQuadTexture(yfixed), new GLPLFragmentShaderTexture2DIndexed(0, alphablend: true)); items.Add(shader); renderstate = GLRenderState.Quads(); renderstate.CullFace = cullface; renderstate.DepthTest = depthtest; renderstate.ClipDistanceEnable = 1; // we are going to cull primitives which are deleted texmipmaplevels = mipmaplevels; this.textureformat = textureformat; }
/// <summary> /// Create a tape with element indexes. Use with a TriangleStrip /// The tape is segmented, and roty determines if its flat to Y or not /// The series of tapes with a margin between them. /// This provides the element index buffer indices as well /// Aligns the element indexes start point for each tape to modulo N to allow trianglestrip to work properly (4 normally) /// You pass in colour array, or null, to add colour information to tape W values. Note colour is one less in length than points array /// </summary> /// <param name="points">Points along which the tape should go. Minimum 2 points</param> /// <param name="colours">Array of colours to use, per segment. Color is packed into W of Vector4 positions on every vertex of a segment</param> /// <param name="width">Tape width</param> /// <param name="segmentlength">Segment length, for each triangle pair making up the tape</param> /// <param name="rotationaroundyradians">Rotate the tape around its axis</param> /// <param name="margin">Space between each segment</param> /// <param name="restartindex">The element restart index to use</param> /// <param name="modulo">The alignment of each segment start vertex index, to align for triangle strip use</param> /// <returns>Return list of points, and element buffer indexes, and the indexor draw element type</returns> public static Tuple <List <Vector4>, List <uint>, DrawElementsType> CreateTape(Vector4[] points, Color[] colours, float width, float segmentlength = 1, float rotationaroundyradians = 0, float margin = 0, uint restartindex = 0xffffffff, int modulo = 4) { List <Vector4> vec = new List <Vector4>(); List <uint> eids = new List <uint>(); DrawElementsType det = DrawElementsType.UnsignedByte; if (points.Length >= 2) { uint vno = 0; for (int i = 0; i < points.Length - 1; i++) { Vector4[] segment = CreateTape(points[i].ToVector3(), points[i + 1].ToVector3(), width, segmentlength, rotationaroundyradians, margin); if (colours != null) { int w = colours[i].PackRGB(); for (int j = 0; j < segment.Length; j++) { segment[j].W = w; } } while (vec.Count % modulo != 0) // must be on a boundary of four for the vertex shaders which normally are used { vec.Add(new Vector4(1000, 2000, 3000, 1)); // dummy value we can recognise vno++; } // System.Diagnostics.Debug.WriteLine($"At {vno} vec {vec.Count} add {vec1.Length}"); vec.AddRange(segment); for (int l = 0; l < segment.Length; l++) { eids.Add(vno++); } eids.Add(restartindex); } eids.RemoveAt(eids.Count - 1); // remove last restart det = GL4Statics.DrawElementsTypeFromMaxEID(vno - 1); } return(new Tuple <List <Vector4>, List <uint>, DrawElementsType>(vec, eids, det)); }
private void IntCreatePath(GLItemsList items, GLRenderProgramSortedList rObjects, GLStorageBlock bufferfindresults) { HistoryEntry lastone = lastpos != -1 && lastpos < currentfilteredlist.Count ? currentfilteredlist[lastpos] : null; // see if lastpos is there, and store it if (TravelPathEndDateEnable || TravelPathStartDateEnable) { currentfilteredlist = unfilteredlist.Where(x => (!TravelPathStartDateEnable || x.EventTimeUTC >= TravelPathStartDate) && (!TravelPathEndDateEnable || x.EventTimeUTC <= TravelPathEndDate)).ToList(); if (currentfilteredlist.Count > MaxStars) { currentfilteredlist = currentfilteredlist.Skip(currentfilteredlist.Count - MaxStars).ToList(); } } else { if (unfilteredlist.Count > MaxStars) { currentfilteredlist = unfilteredlist.Skip(currentfilteredlist.Count - MaxStars).ToList(); } else { currentfilteredlist = unfilteredlist; } } // do date filter on currentfilteredlist lastpos = lastone == null ? -1 : currentfilteredlist.IndexOf(lastone); // may be -1, may have been removed var positionsv4 = currentfilteredlist.Select(x => new Vector4((float)x.System.X, (float)x.System.Y, (float)x.System.Z, 0)).ToArray(); var colours = currentfilteredlist.Select(x => x.JumpColor).ToArray(); float seglen = tapesize * 10; // a tape is a set of points (item1) and indexes to select them (item2), so we need an element index in the renderer to use. var tape = GLTapeObjectFactory.CreateTape(positionsv4, colours, tapesize, seglen, 0F.Radians(), margin: sunsize * 1.2f); if (ritape == null) // first time.. { // first the tape var tapetex = new GLTexture2D(Properties.Resources.chevron, internalformat: OpenTK.Graphics.OpenGL4.SizedInternalFormat.Rgba8); // tape image items.Add(tapetex); tapetex.SetSamplerMode(OpenTK.Graphics.OpenGL4.TextureWrapMode.Repeat, OpenTK.Graphics.OpenGL4.TextureWrapMode.Repeat); tapefrag = new GLPLFragmentShaderTextureTriStripColorReplace(1, Color.FromArgb(255, 206, 0, 0)); var vert = new GLPLVertexShaderWorldTextureTriStrip(); tapeshader = new GLShaderPipeline(vert, tapefrag); items.Add(tapeshader); GLRenderState rts = GLRenderState.Tri(tape.Item3, cullface: false); // set up a Tri strip, primitive restart value set from tape, no culling rts.DepthTest = depthtest; // no depth test so always appears // now the renderer, set up with the render control, tape as the points, and bind a RenderDataTexture so the texture gets binded each time ritape = GLRenderableItem.CreateVector4(items, OpenTK.Graphics.OpenGL4.PrimitiveType.TriangleStrip, rts, tape.Item1.ToArray(), new GLRenderDataTexture(tapetex)); tapepointbuf = items.LastBuffer(); // keep buffer for refill ritape.Visible = tape.Item1.Count > 0; // no items, set not visible, so it won't except over the BIND with nothing in the element buffer ritape.CreateElementIndex(items.NewBuffer(), tape.Item2.ToArray(), tape.Item3); // finally, we are using index to select vertexes, so create an index rObjects.Add(tapeshader, "travelpath-tape", ritape); // add render to object list // now the stars starposbuf = items.NewBuffer(); // where we hold the vertexes for the suns, used by renderer and by finder starposbuf.AllocateFill(positionsv4); //Vector4[] vectors = starposbuf.ReadVector4s(0, starposbuf.Length / 16); sunvertex = new GLPLVertexShaderModelCoordWorldAutoscale(new Color[] { Color.Yellow, Color.FromArgb(255, 230, 230, 1) }, autoscale: 30, autoscalemin: 1f, autoscalemax: 2f, useeyedistance: false); // below scale, 1f, above scale, scale up to x times (eyedist/scale) sunshader = new GLShaderPipeline(sunvertex, new GLPLStarSurfaceFragmentShader()); items.Add(sunshader); var shape = GLSphereObjectFactory.CreateSphereFromTriangles(2, sunsize); GLRenderState rt = GLRenderState.Tri(); // render is triangles, with no depth test so we always appear rt.DepthTest = depthtest; rt.DepthClamp = true; renderersun = GLRenderableItem.CreateVector4Vector4(items, OpenTK.Graphics.OpenGL4.PrimitiveType.Triangles, rt, shape, starposbuf, 0, null, currentfilteredlist.Count, 1); rObjects.Add(sunshader, "travelpath-suns", renderersun); // find compute var geofind = new GLPLGeoShaderFindTriangles(bufferfindresults, 16); findshader = items.NewShaderPipeline(null, sunvertex, null, null, geofind, null, null, null); rifind = GLRenderableItem.CreateVector4Vector4(items, OpenTK.Graphics.OpenGL4.PrimitiveType.Triangles, GLRenderState.Tri(), shape, starposbuf, ic: currentfilteredlist.Count, seconddivisor: 1); // Sun names, handled by textrenderer textrenderer = new GLBitmaps("bm-travelmap", rObjects, new Size(128, 40), depthtest: depthtest, cullface: false); items.Add(textrenderer); } else { tapepointbuf.AllocateFill(tape.Item1.ToArray()); // replace the points with a new one ritape.RenderState.PrimitiveRestart = GL4Statics.DrawElementsRestartValue(tape.Item3); // IMPORTANT missing bit Robert, must set the primitive restart value to the new tape size ritape.CreateElementIndex(ritape.ElementBuffer, tape.Item2.ToArray(), tape.Item3); // update the element buffer ritape.Visible = tape.Item1.Count > 0; starposbuf.AllocateFill(positionsv4); // and update the star position buffers so find and sun renderer works renderersun.InstanceCount = positionsv4.Length; // update the number of suns to draw. rifind.InstanceCount = positionsv4.Length; // update the find list } // name bitmaps HashSet <object> hashset = new HashSet <object>(currentfilteredlist); // so it can find it quickly textrenderer.CurrentGeneration++; // setup for next generation textrenderer.RemoveGeneration(textrenderer.CurrentGeneration - 1, hashset); // and remove all of the previous one which are not in hashset. Font fnt = new Font("Arial", 8.5F); using (StringFormat fmt = new StringFormat()) { fmt.Alignment = StringAlignment.Center; foreach (var isys in currentfilteredlist) { if (textrenderer.Exist(isys) == false) // if does not exist already, need a new label { textrenderer.Add(isys, isys.System.Name, fnt, Color.White, Color.Transparent, new Vector3((float)isys.System.X, (float)isys.System.Y - 5, (float)isys.System.Z), new Vector3(20, 0, 0), new Vector3(0, 0, 0), textformat: fmt, rotatetoviewer: true, rotateelevation: false, alphafadescalar: -200, alphafadepos: 300); } } } fnt.Dispose(); }
/// <summary> /// Creator of this draw set /// </summary> /// <param name="textures"> number of 2D textures to allow maximum (limited by GL)</param> /// <param name="estimateditemspergroup">Estimated objects per group, this adds on vertext buffer space to allow for mat4 alignment. Smaller means more allowance.</param> /// <param name="mingroups">Minimum groups to have</param> /// <param name="objectbuffer">Object buffer to use</param> /// <param name="objectvertexes">Number of object vertexes</param> /// <param name="objrc">The object render state control</param> /// <param name="objpt">The object draw primitive type</param> /// <param name="texturesize">The size of the label</param> /// <param name="textrc">The text render state</param> /// <param name="textureformat">The texture format for the text</param> /// <param name="debuglimittexture">For debug, set this to limit maximum number of entries. 0 = off</param> /// <returns></returns> public Tuple <GLRenderableItem, GLRenderableItem> Create( int textures, int estimateditemspergroup, int mingroups, GLBuffer objectbuffer, int objectvertexes, GLRenderState objrc, PrimitiveType objpt, Size texturesize, GLRenderState textrc, SizedInternalFormat textureformat, int debuglimittexture = 0) { this.objectvertexescount = objectvertexes; this.context = GLStatics.GetContext(); // Limit number of 2d textures in a single 2d array int maxtextper2darray = GL4Statics.GetMaxTextureDepth(); if (debuglimittexture > 0) { maxtextper2darray = debuglimittexture; } // set up number of textmaps bound int maxtexturesbound = GL4Statics.GetMaxFragmentTextures(); int textmaps = Math.Min(textures, maxtexturesbound); // which then give us the number of stars we can do int objectcount = textmaps * maxtextper2darray; int groupcount = objectcount / estimateditemspergroup; groupcount = Math.Max(mingroups, groupcount); // min groups // System.Diagnostics.Debug.WriteLine($"GLObjectWithLabels oc {objectcount} gc {groupcount}"); // estimate maximum vert buffer needed, allowing for extra due to the need to align the mat4 int vertbufsize = objectcount * (GLBuffer.Vec4size + GLBuffer.Mat4size) + // for a vec4 + mat4 per object groupcount * GLBuffer.Mat4size; // and for groupcount Mat4 fragmentation per group // create the vertex indirect buffer dataindirectbuffer = new GLVertexBufferIndirect(items, vertbufsize, GLBuffer.WriteIndirectArrayStride * groupcount, true, BufferUsageHint.DynamicDraw); // objects ObjectRenderer = GLRenderableItem.CreateVector4Vector4(items, objpt, objrc, objectbuffer, 0, 0, // binding 0 is shapebuf, offset 0, no draw count yet dataindirectbuffer.Vertex, 0, // binding 1 is vertex's world positions, offset 0 null, 0, 1); // no ic, second divisor 1 ObjectRenderer.BaseIndexOffset = 0; // offset in bytes where commands are stored ObjectRenderer.MultiDrawCountStride = GLBuffer.WriteIndirectArrayStride; // text this.textures = new GLTexture2DArray[textmaps]; for (int i = 0; i < this.textures.Length; i++) { int n = Math.Min(objectcount, maxtextper2darray); this.textures[i] = new GLTexture2DArray(texturesize.Width, texturesize.Height, n, textureformat, 1); items.Add(this.textures[i]); objectcount -= maxtextper2darray; } TextRenderer = GLRenderableItem.CreateMatrix4(items, PrimitiveType.Quads, textrc, dataindirectbuffer.Vertex, 0, 0, //attach buffer with matrices, no draw count new GLRenderDataTexture(this.textures, 0), // binding 0 assign to our texture 2d 0, 1); //no ic, and matrix divide so 1 matrix per vertex set TextRenderer.BaseIndexOffset = 0; // offset in bytes where commands are stored TextRenderer.MultiDrawCountStride = GLBuffer.WriteIndirectArrayStride; return(new Tuple <GLRenderableItem, GLRenderableItem>(ObjectRenderer, TextRenderer)); }
/// <summary> /// Get a texture image in a speciic type. Only floats or bytes are currently supported. /// Use inverty to correct for any inversion if your getting the data from a framebuffer texture - it appears to be inverted when written /// </summary> /// <typeparam name="T"></typeparam> /// <param name="pxformatback">Return format.</param> /// <param name="level">Image level</param> /// <param name="inverty">If to inverty so the image is the standard windows way up (first is top) instead of openGL</param> /// <returns>Array of selected type</returns> public T[] GetTextureImageAs <T>(PixelFormat pxformatback = PixelFormat.Rgba, int level = 0, bool inverty = false) { System.Diagnostics.Debug.Assert(context == GLStatics.GetContext(), "Context incorrect"); int elementsperpixel = GL4Statics.ElementsPerPixel(pxformatback); int totalelements = Width * Height * elementsperpixel; int elementsizeT = Marshal.SizeOf(typeof(T)); int elementstride = elementsperpixel * Width; int bufsize = totalelements * elementsizeT; IntPtr unmanagedPointer = Marshal.AllocHGlobal(bufsize); // get an unmanaged buffer GL.GetTextureImage(Id, level, pxformatback, elementsizeT == 4 ? PixelType.Float : PixelType.UnsignedByte, bufsize, unmanagedPointer); // fill GLStatics.Check(); if (elementsizeT == 4) { float[] data = new float[totalelements]; if (inverty) { IntPtr p = unmanagedPointer; for (int y = 0; y < Height; y++) { Marshal.Copy(p, data, (Height - 1 - y) * elementstride, elementstride); // transfer buffer to floats p += elementstride; } } else { Marshal.Copy(unmanagedPointer, data, 0, totalelements); // transfer buffer to floats } Marshal.FreeHGlobal(unmanagedPointer); return(data as T[]); } else { byte[] data = new byte[totalelements]; if (inverty) { IntPtr p = unmanagedPointer; for (int y = 0; y < Height; y++) { Marshal.Copy(p, data, (Height - 1 - y) * elementstride, elementstride); // transfer buffer to floats p += elementstride; } } else { Marshal.Copy(unmanagedPointer, data, 0, totalelements); // transfer buffer to floats } Marshal.FreeHGlobal(unmanagedPointer); return(data as T[]); } }
// currentfilteredlist set, go.. private void CreatePathInt(Color?tapepathdefault = null) { if (!tapeshader.Compiled || !sunshader.Compiled) { return; } // Note W here selects the colour index of the stars, 0 = first, 1 = second etc Vector4[] positionsv4 = currentfilteredlistsys.Select(x => new Vector4((float)x.X, (float)x.Y, (float)x.Z, 0)).ToArray(); // positionsv4 = positionsv4.Take(2).ToArray(); // debug Color[] color = new Color[currentfilteredlistsys.Count]; if (currentfilteredlisthe != null) { for (int i = 0; i < positionsv4.Length; i++) // if we are on a jump colour entry, then pick up its colour, otherwise use the last, unless its at the beginning { var je = currentfilteredlisthe[i].journalEntry as IJournalJumpColor; if (je != null) { color[i] = je.MapColorARGB; } else { color[i] = i > 0 ? color[i - 1] : Color.Green; } } } else { for (int i = 0; i < positionsv4.Length; i++) // for an ISystem path, we use the default color { color[i] = tapepathdefault.Value; } } float seglen = tapesize * 10; // set the tape up var tape = GLTapeNormalObjectFactory.CreateTape(positionsv4, color, seglen, 0F.Radians(), margin: sunsize * 1.2f); // create tape // we fill in the buffer again, and reset the binding position of entry two, update the element index, set visibility tapepointbuf.AllocateBytes((tape.Item1.Count + tape.Item2.Count) * GLBuffer.Vec4size); tapepointbuf.Fill(tape.Item1.ToArray()); tapepointbuf.Fill(tape.Item2.ToArray()); tapepointbuf.Bind(ritape.VertexArray, 1, tapepointbuf.Positions[1], 16); // for the second one, need to update and rebind positions. First one always at zero ritape.RenderState.PrimitiveRestart = GL4Statics.DrawElementsRestartValue(tape.Item4); // IMPORTANT missing bit Robert, must set the primitive restart value to the new tape size ritape.CreateElementIndex(ritape.ElementBuffer, tape.Item3.ToArray(), tape.Item4); // update the element buffer, DrawCount, ElementIndexSize ritape.Visible = tape.Item1.Count > 0; // only visible if positions.. starposbuf.AllocateFill(positionsv4); // and update the star position buffers so find and sun renderer works renderersun.InstanceCount = positionsv4.Length; // update the number of suns to draw. renderersun.Visible = positionsv4.Length > 0; // only visible if positions.. rifind.InstanceCount = positionsv4.Length; // update the find list // name bitmaps HashSet <object> hashset = new HashSet <object>(currentfilteredlistsys); // so it can find it quickly textrenderer.CurrentGeneration++; // setup for next generation textrenderer.RemoveGeneration(textrenderer.CurrentGeneration - 1, hashset); // and remove all of the previous one which are not in hashset. using (StringFormat fmt = new StringFormat()) { fmt.Alignment = StringAlignment.Center; foreach (var isys in currentfilteredlistsys) { if (textrenderer.Exist(isys) == false) // if does not exist already, need a new label { textrenderer.Add(isys, isys.Name, Font, ForeText, BackText, new Vector3((float)isys.X + LabelOffset.X, (float)isys.Y + LabelOffset.Y, (float)isys.Z + LabelOffset.Z), LabelSize, new Vector3(0, 0, 0), textformat: fmt, rotatetoviewer: true, rotateelevation: false, alphafadescalar: -200, alphafadepos: 300); } } } }