using System; using System.Numerics; using AcDream.App.Rendering.Wb; using AcDream.App.UI; using Silk.NET.OpenGL; namespace AcDream.App.Studio; /// /// Renders a into an off-screen FBO each frame and /// returns the color texture handle for display in ImGui. /// /// Pattern lifted verbatim from : /// RGBA8 color texture + Depth24Stencil8 renderbuffer, lazily (re)created on /// size change. The entire 2-D UI pass is sealed in a /// so it cannot disturb the surrounding ImGui GL state. /// /// FBO origin is bottom-left (GL convention). The caller must flip V when /// displaying the texture in ImGui (pass uv0=(0,1), uv1=(1,0) to ImGui.Image) /// so the image appears right-side-up in ImGui's top-left coordinate system. /// public sealed unsafe class PanelFbo : IDisposable { private readonly GL _gl; // Off-screen target — lazily (re)created when the requested size changes. private uint _fbo; private uint _colorTex; private uint _depthRbo; private int _fbW; private int _fbH; public PanelFbo(GL gl) { _gl = gl ?? throw new ArgumentNullException(nameof(gl)); } /// /// Render (a full draw pass) into a /// private FBO at × pixels. /// Returns the GL color texture handle (0 on failure). The texture is valid until /// the next call to with a different size, or until . /// public uint Render(int width, int height, UiHost host) { if (width <= 0 || height <= 0 || host is null) return 0u; EnsureFramebuffer(width, height); if (_fbo == 0) return 0u; // Seal the entire pass: GLStateScope saves + restores every GL state the // UI draw touches (viewport, blend, FBO binding, etc.) so ImGui's own state // — set up by BeginFrame and expected intact by Render — is untouched. using var scope = new GLStateScope(_gl); _gl.BindFramebuffer(FramebufferTarget.Framebuffer, _fbo); _gl.Viewport(0, 0, (uint)width, (uint)height); _gl.Disable(EnableCap.ScissorTest); _gl.ClearColor(0.18f, 0.18f, 0.18f, 1f); // opaque dark-grey canvas background (the FBO IS the canvas) _gl.ClearDepth(1.0); _gl.DepthMask(true); _gl.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); host.Draw(new Vector2(width, height)); // FBO stays bound here; GLStateScope.Dispose() restores the previous binding. return _colorTex; } // ── FBO lifecycle (mirrors PaperdollViewportRenderer.EnsureFramebuffer) ────── private void EnsureFramebuffer(int width, int height) { if (_fbo != 0 && width == _fbW && height == _fbH) return; DeleteFramebuffer(); _fbW = width; _fbH = height; _colorTex = _gl.GenTexture(); _gl.BindTexture(TextureTarget.Texture2D, _colorTex); _gl.TexImage2D(TextureTarget.Texture2D, 0, InternalFormat.Rgba8, (uint)width, (uint)height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, (void*)0); _gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); _gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMinFilter.Linear); _gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge); _gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge); _gl.BindTexture(TextureTarget.Texture2D, 0); _depthRbo = _gl.GenRenderbuffer(); _gl.BindRenderbuffer(RenderbufferTarget.Renderbuffer, _depthRbo); _gl.RenderbufferStorage(RenderbufferTarget.Renderbuffer, InternalFormat.Depth24Stencil8, (uint)width, (uint)height); _gl.BindRenderbuffer(RenderbufferTarget.Renderbuffer, 0); _fbo = _gl.GenFramebuffer(); _gl.BindFramebuffer(FramebufferTarget.Framebuffer, _fbo); _gl.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2D, _colorTex, 0); _gl.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.DepthStencilAttachment, RenderbufferTarget.Renderbuffer, _depthRbo); var status = _gl.CheckFramebufferStatus(FramebufferTarget.Framebuffer); _gl.BindFramebuffer(FramebufferTarget.Framebuffer, 0); if (status != GLEnum.FramebufferComplete) { Console.WriteLine($"[studio] PanelFbo incomplete: {status} ({width}x{height})"); DeleteFramebuffer(); } } private void DeleteFramebuffer() { if (_fbo != 0) { _gl.DeleteFramebuffer(_fbo); _fbo = 0; } if (_colorTex != 0) { _gl.DeleteTexture(_colorTex); _colorTex = 0; } if (_depthRbo != 0) { _gl.DeleteRenderbuffer(_depthRbo); _depthRbo = 0; } _fbW = _fbH = 0; } public void Dispose() => DeleteFramebuffer(); }