Replace legacy OpenGL implementation with Silk.NET-based renderer - #339
Open
dsn27 wants to merge 10 commits into
Open
Replace legacy OpenGL implementation with Silk.NET-based renderer#339dsn27 wants to merge 10 commits into
dsn27 wants to merge 10 commits into
Conversation
Replace the OpenGL 1.x fixed-function pipeline with a modern, shader-based renderer built on Silk.NET.OpenGL. The rendering implementation moves out of the netstandard2.0 core (CADability) and into the WinForms host (CADability.Forms), which owns the platform GL context. Core (CADability, netstandard2.0): - Remove OpenGL.cs (the ~52k-line Gl/Glu/Wgl/User/Kernel P/Invoke bindings) and OpenGlList.cs; the core no longer talks to OpenGL. - Trim Gdi.cs to the small GDI32 stub still needed for kerning-pair queries (CreateCompatibleDC/DeleteDC/SelectObject/DeleteObject/ GetKerningPairs). - Add the IPaintTo3DFlatText interface so text glyphs can be drawn unlit (in their ColorDef color) instead of being shaded like a 3D surface. - Text.cs: measure glyph advance widths via GDI+ MeasureString (GenericTypographic) instead of the removed GetCharABCWidths P/Invoke, and drive FlatTextMode while tesselating glyphs. Forms (CADability.Forms, net8.0-windows): - Add PaintToSilkGL, the IPaintTo3D renderer that replaces PaintToOpenGL. - Add WglContext for WGL context creation and Silk.NET GL binding (with the opengl32.dll fallback for core 1.x entry points). - Add PaintToSilkGLList (OpenGLList.cs), a VAO/VBO-based replacement for fixed-function display lists (interleaved position+normal for surfaces, position for edges, sub-lists for text). - Add GLSL 3.30 core shaders (surface + edge) with two-sided lighting and an unlit path for flat text. - Reference Silk.NET.OpenGL 2.* and embed the shaders as resources. - Wire CadCanvas/CadFrame to PaintToSilkGL. Known deferred work (marked in code): - Off-screen bitmap rendering (PaintToBitmap / CreatePaintInterface) is not yet implemented and throws NotImplementedException. - Line dash patterns and icon/bitmap display are stubbed no-ops. Target frameworks are unchanged: CADability stays netstandard2.0 and the Forms/App/Tests projects stay net8.0-windows. No netDXF or ACadSharp changes are included. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014HjM12HeHU7a7n9DJx91Y4
The old PaintToOpenGL.PaintToBitmap rendered into a GDI bitmap DC using PFD_DRAW_TO_BITMAP, which only works with Microsoft's software OpenGL 1.1 renderer — exactly the fixed-function pipeline the Silk.NET migration removed. Modern hardware-accelerated core contexts refuse bitmap DCs, so this was left throwing NotImplementedException. Replace it with a framebuffer-object path: - Init(Bitmap): creates a hidden NativeWindow to host the WGL context (never shown), compiles the shared shaders, and renders into an FBO (RGBA8 color + Depth24Stencil8 renderbuffers) of the bitmap's size. - FinishPaint(): in off-screen mode reads the FBO back into the target bitmap (BGRA ReadPixels into LockBits, then a vertical flip because GL rows are bottom-up) instead of swapping buffers. - Dispose(): performs a best-effort readback first, because the printing path (LayoutView) disposes the paint interface without ever calling FinishPaint, then deletes the FBO resources and destroys the hidden window. - MakeCurrent(): rebinds the FBO in off-screen mode. - IsBitmap now reports the real off-screen flag, so core code keeps avoiding display lists when painting to a bitmap, as it did with the old renderer. - PaintToSilkGL.PaintToBitmap: full port of the old projection/extent setup, no longer delegating to the throwing OpenGlCustomize stub. - CadFrame.CreatePaintInterface: restored (used by LayoutView printing); no longer throws. The CADability core project is unchanged (stays netstandard2.0); CADability.Forms still targets net8.0-windows. Verified by compiling CADability, CADability.Forms, CADability.App and CADability.Tests with the .NET 8 SDK (EnableWindowsTargeting on Linux), 0 errors. Runtime behavior needs a Windows machine with an OpenGL 3.3 capable driver; note the old reference bitmaps in the tests were produced by the GL 1.1 software rasterizer and may need regeneration for the shader-based renderer. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014HjM12HeHU7a7n9DJx91Y4
dsn27
changed the base branch from
claude/opengl-silk-net-migration-humming-snowflake
to
master
July 22, 2026 10:47
…lkGL The coordinate cross (ModelView.PaintCoordCross, also GDI2DView) draws its x/y/z axis labels by calling IPaintTo3D.Text directly instead of going through a Text GeoObject. PaintToSilkGL.Text was an empty stub — its comment assumed all text arrives pre-triangulated via Triangle(), which is only true for Text GeoObjects honoring TriangulateText — so the axis labels silently disappeared. (In a top view the z label is additionally skipped by the SameDirection checks, which is why exactly "x" and "y" were reported missing.) Implement Text() by routing the call through a transient Text GeoObject: PrePaintTo3D creates the per-character display lists in the font cache (glyphs tessellated at em size 1, rendered unlit via FlatTextMode so labels keep the exact info color), PaintTo3D replays them with the placement, alignment and kerning of the em-based recipe that the old wglUseFontOutlines implementation also used. The transient call forces TriangulateText=true, because with TriangulateText=false Text.PaintTo3D would call back into IPaintTo3D.Text and recurse. Verified by compiling CADability and CADability.Forms (0 errors); visual verification needs a Windows machine. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014HjM12HeHU7a7n9DJx91Y4
The coordinate cross is painted right after ProjectedModel.Paint, which ends with PaintFaces(CurvesOnly) — so paintSurfaces is false while the background is drawn (ModelView only restores PaintFaces(All) after PaintBackground). The glyphs of the x/y/z labels replay through List(), which skips DrawListSurface when paintSurfaces is false, so the labels stayed invisible while the axis lines (Polyline) and arrows (Triangle) drew fine, as immediate-mode calls are not gated. The old renderer's IPaintTo3D.Text called glCallList directly without any paint-mode gate, so direct text always drew. Restore that behavior by forcing paintSurfaces/paintEdges on for the duration of the transient Text paint and restoring them afterwards. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014HjM12HeHU7a7n9DJx91Y4
…rites Inserted bitmaps (Picture GeoObjects) were invisible because RectangularBitmap and the whole Prepare/Display bitmap family were no-op stubs in PaintToSilkGL. Add a texture pipeline replacing the old fixed-function texture and raster operations: - texture.vert/.frag: textured draw with u_color tint and discard at alpha <= 0.5, matching the old glAlphaFunc(GL_GREATER, 0.5). - PrepareBitmap(Bitmap): uploads the bitmap as an RGBA8 texture (NEAREST filter, rows flipped to GL orientation), cached per Bitmap. - RectangularBitmap: textured world-space quad with the same corner/ texcoord layout as the old GL_QUADS path; records into the display list (new TexQuad entries in PaintToSilkGLList) or draws immediately. - PrepareBitmap(Bitmap,x,y) + DisplayBitmap: screen-aligned unscaled sprite with pixel anchor, replacing glRasterPos/glDrawPixels (Icon GeoObjects). - PrepareIcon + DisplayIcon: white alpha-mask texture tinted with the current color, centered on the point, replacing glBitmap (hotspot markers). - List() replays TexQuads/Sprites in the faces phase only, so the two-phase (faces+curves) replay of categorized display lists draws them once. - Dispose deletes the texture program and all cached textures. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014HjM12HeHU7a7n9DJx91Y4
SetLinePattern and PreparePointSymbol were the last visible no-op stubs in PaintToSilkGL: all line styles rendered solid and Point objects drew as plain dots. Dashed lines (glLineStipple replacement): - Edge vertices carry a second attribute: the cumulative distance along the polyline in world units (edge stride 3 -> 4 floats), recorded at tessellation time, so dash phase continues across segments instead of restarting at every tessellation segment. - The edge fragment shader converts that distance to screen pixels (u_distScale from PixelToWorld) and discards fragments in the gap segments of the pattern (u_pattern[8], noperspective interpolation). - SetLinePattern normalizes patterns like the old stipple code: only the proportions are used, repeating over 16 screen pixels with at least 1 px per segment; null/empty/degenerate patterns mean solid. - Display list edge batches now carry (color, pattern) so per-entity patterns replay correctly; 2D overlay lines stay solid. Point symbols (glBitmap replacement): - Port the BitmapList point symbol strip loader (PointSymbols.bmp / PointSymbolsB.bmp via BitmapTable + ImageList, select square at 12). - Points() maps symbol flags to the same bitmaps as the old renderer (base symbol & 0x07, Circle/Square modifiers, Select overrides all, bold offset 6 when UseLineWidth) and draws them as screen-aligned sprites via DisplayIcon in the current color, which also records correctly into display lists. A pure Dot symbol now uses the dot bitmap instead of a 1 px GL_POINTS vertex, making it list-safe. - PreparePointSymbol pre-uploads the mask textures. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014HjM12HeHU7a7n9DJx91Y4
…feedback 1. Points/dimensions invisible (deselected dimension: text vanished, arrows hollow; drawn points not visible): List() gated surface content on paintSurfaces and sprites on the faces phase. But the old glCallList replayed the entire list unconditionally — the PaintFaces mode controls what core code emits while recording (and the polygon offset), not what an already recorded list draws. Curve-phase lists legitimately contain surface geometry (dimension text, filled arrow heads, point symbol sprites). Replay everything unconditionally. 2. Massive memory consumption (e.g. after adding a sphere): dropped display lists never freed their GPU buffers — the old OpenGlList finalizer/FreeLists pattern was lost in the migration, and nothing calls IPaintTo3DList.Dispose. The unscaled lists are rebuilt on every zoom change and construction feedback creates lists per frame, so VRAM grew without bound. PaintToSilkGLList now queues its VAO/VBO ids in a finalizer; FreeDeleted deletes them on the render thread from OpenList and FreeUnusedLists. Also release the CPU-side vertex arrays after GPU upload — they were kept alive forever, roughly doubling every display list's memory. 3. Split view black (second view showed nothing): every canvas created its own WGL context, but the categorized display lists live on the Model and are painted by all views — VAOs from one context are invalid names in another. All views (and the off-screen FBO path) now share one process-wide WGL context, made current per canvas against that canvas' DC (replacement for the old MainRenderContext + wglShareLists). Dispose no longer deletes the context or the shared GL api object, only per-instance resources and the DC. 4. Sphere shading "unsmooth": the specular term used the direction toward the world origin (-vFragPos) as the view vector, smearing the highlight into irregular bands across curved surfaces. Use the constant per-view eye direction (orthographic viewer at infinity, like the old fixed-function pipeline) and the old broad shininess (5 instead of 32, at half strength since per-fragment specular is punchier than Gouraud). 5. Trim feedback grey instead of yellow: ActionFeedBack calls SelectedList(list, -1), but the wobble loop never runs for radius <= 0 and the center pass forced the original colors. Implement the old radius <= 0 semantics: draw the whole list once in the select color, translated 2*precision toward the viewer so the highlight wins the depth test against the object itself. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014HjM12HeHU7a7n9DJx91Y4
The zoom-in memory explosion with curved solids (70 MB -> 1.5 GB with a sphere) had a second ingredient besides the missing finalizer: after UploadToGpu nulls the CPU arrays, a dead display list is a tiny managed object holding megabytes of invisible driver memory — the GC sees no reason to ever collect, so the finalizers that queue the GPU buffers for deletion never run. Display lists are rebuilt on every zoom step (re-triangulation at finer precision, unscaled lists), so driver memory grew without bound. 2D objects produce tiny lists, which is why they did not show the effect. - UploadToGpu registers the buffer size via GC.AddMemoryPressure; Dispose and the finalizer remove it, so the GC collects dropped lists roughly as if their geometry still lived on the managed heap. - MakeCurrent (start of every frame) now also processes the pending deletion queue, so reclaim does not wait for the next OpenList. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014HjM12HeHU7a7n9DJx91Y4
…e phase Dimensions had no PaintTo3DList override, so the base implementation registered them for the curve category only. Curve display lists are recorded under PaintFaces(CurvesOnly), i.e. PaintSurfaces == false — and a dimension's text glyphs and filled arrow heads (solid Hatch) are Face geometry whose PaintTo3D skips emission in that mode. The text and arrow fillings therefore never made it INTO the recorded list; fixing the replay side could not help. The old renderer got away with it because its default text path (TriangulateText == false, wglUseFontOutlines) bypassed Face.PaintTo3D entirely. - Dimension now registers for both categories (lists.Add(layer, true, true, this)) like solids do: the face phase records the text and arrow faces, the curve phase records the dimension/extension lines. - PaintToSilkGL.PaintSurfaces additionally reports true while FlatTextMode is active, so glyph display lists created lazily during curve-phase recording are never cached as empty. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014HjM12HeHU7a7n9DJx91Y4
Big faces add millions of floats per call; growing the List<float> by repeated doubling roughly doubles the transient allocations. Reserve the needed capacity in one step. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014HjM12HeHU7a7n9DJx91Y4
dsn27
force-pushed
the
claude/opengl-silk-net-migration-6pk28g
branch
from
July 22, 2026 14:28
1bb12b0 to
f13d119
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR replaces the legacy Windows Forms OpenGL implementation with a modern Silk.NET-based renderer (
PaintToSilkGL). The change modernizes the 3D rendering pipeline while maintaining API compatibility through the existingIPaintTo3Dinterface.Key Changes
Removed legacy OpenGL code:
CADability/OpenGL.cs(51,941 lines) - Legacy OpenGL bindingsCADability.Forms/PaintToOpenGL.cs(2,278 lines) - Old Windows Forms OpenGL rendererCADability/OpenGlList.cs(140 lines) - Legacy display list implementationAdded Silk.NET-based implementation:
CADability.Forms/PaintToSilkGL.cs(1,040 lines) - New modern OpenGL renderer using Silk.NETCADability.Forms/WglContext.cs(127 lines) - WGL context management for WindowsCADability.Forms/OpenGLList.cs(98 lines) - Display list implementation for Silk.NETsurface.vert/surface.frag- Phong lighting for surfacesedge.vert/edge.frag- Simple edge renderingUpdated integration points:
CADability.Forms/CadCanvas.cs- Updated type check fromPaintToOpenGLtoPaintToSilkGLCADability.Forms/CadFrame.cs- Updated factory to instantiatePaintToSilkGLCADability/PaintToOpenGl.cs- AddedIPaintTo3DFlatTextinterface extensionCADability.Forms/CADability.Forms.csproj- Enabled unsafe code blockstests/CADability.Tests/ProjectTest.cs- Updated test referencesSimplified GDI bindings:
CADability/Gdi.cs- Reduced from 609 to 24 lines, keeping only essential P/Invoke declarationsImplementation Details
PaintToSilkGLclass implements bothIPaintTo3DandIPaintTo3DFlatTextinterfaces, maintaining backward compatibilityhttps://claude.ai/code/session_014HjM12HeHU7a7n9DJx91Y4