1 module des.gl.base.frame;
2 
3 import std.exception;
4 import std.conv;
5 
6 import derelict.opengl3.gl3;
7 
8 import des.math.linear;
9 
10 import des.gl.base.texture;
11 import des.gl.base.type;
12 import des.gl.base.rbo;
13 
14 import des.il.image;
15 
16 ///
17 class GLFBOException : DesGLException 
18 {
19     ///
20     this( string msg, string file=__FILE__, size_t line=__LINE__ ) @safe pure nothrow
21     { super( msg, file, line ); }
22 }
23 
24 ///
25 class GLFrameBuffer : DesObject
26 {
27     mixin DES;
28     mixin ClassLogger;
29 
30 protected:
31     uint _id;
32     static uint[] id_stack;
33 
34     ///
35     enum Attachment
36     {
37         COLOR         = GL_COLOR_ATTACHMENT0,        /// `GL_COLOR_ATTACHMENT0`
38         DEPTH         = GL_DEPTH_ATTACHMENT,         /// `GL_DEPTH_ATTACHMENT`
39         STENCIL       = GL_STENCIL_ATTACHMENT,       /// `GL_STENCIL_ATTACHMENT`
40         DEPTH_STENCIL = GL_DEPTH_STENCIL_ATTACHMENT, /// `GL_DEPTH_STENCIL_ATTACHMENT`
41     }
42 
43 public:
44 
45     /// `glGenFramebuffers`
46     this()
47     {
48         if( id_stack.length == 0 ) id_stack ~= 0;
49 
50         checkGLCall!glGenFramebuffers( 1, &_id );
51         logger = new InstanceLogger( this, format( "%d", _id ) );
52         logger.Debug( "pass" );
53     }
54 
55     final pure const @property
56     {
57         ///
58         uint id() { return _id; }
59     }
60 
61     final nothrow
62     {
63         /// `glBindFramebuffer` add id to stack 
64         void bind()
65         {
66             if( id_stack[$-1] == _id ) return;
67             ntCheckGLCall!glBindFramebuffer( GL_FRAMEBUFFER, _id );
68             id_stack ~= _id;
69             debug logger.trace( "pass" );
70         }
71 
72         /// pop from stack old frame buffer id and `glBindFramebuffer` with it
73         void unbind()
74         {
75             if( id_stack.length < 2 && id_stack[$-1] != _id ) return;
76             id_stack.length--;
77             ntCheckGLCall!glBindFramebuffer( GL_FRAMEBUFFER, id_stack[$-1] );
78             debug logger.trace( "bind [%d]", id_stack[$-1] );
79         }
80     }
81 
82     ///
83     void drawBuffers( in int[] bufs... )
84     {
85         int max_bufs;
86         checkGLCall!glGetIntegerv( GL_MAX_DRAW_BUFFERS, &max_bufs );
87         enforce( bufs.length < max_bufs,
88             new GLFBOException( format( "count of draw buffers greater what max value (%d>%d)", bufs.length, max_bufs ) ) );
89         bind(); scope(exit) unbind();
90         GLenum[] res;
91         foreach( val; bufs )
92             if( val < 0 ) res ~= GL_NONE;
93             else res ~= cast(GLenum)( Attachment.COLOR + val );
94         checkGLCall!glDrawBuffers( cast(int)res.length, res.ptr );
95     }
96 
97     /// set render buffer as depth attachment
98     void setRBODepth( GLRenderBuffer rbo )
99     in{ assert( rbo !is null ); } body
100     {
101         bind(); scope(exit) unbind();
102         setRBO( rbo, Attachment.DEPTH );
103         logger.Debug( "[%d]", rbo.id );
104     }
105 
106     /// set render buffer as color attachment
107     void setRBOColor( GLRenderBuffer rbo, uint no )
108     in{ assert( rbo !is null ); } body
109     {
110         bind(); scope(exit) unbind();
111         setRBO( rbo, cast(GLenum)( Attachment.COLOR + no ) );
112         logger.Debug( "[%d] as COLOR%d", rbo.id, no );
113     }
114 
115     /// set texture as depth attachment
116     void setTexDepth( GLTexture tex )
117     {
118         bind(); scope(exit) unbind();
119         setTex( tex, Attachment.DEPTH );
120         logger.Debug( "[%d]", tex.id );
121     }
122 
123     /// set texture as color attachment
124     void setTexColor( GLTexture tex, uint no=0 )
125     {
126         bind(); scope(exit) unbind();
127         setTex( tex, cast(GLenum)( Attachment.COLOR + no ) );
128         logger.Debug( "[%d] as COLOR%d", tex.id, no );
129     }
130 
131     /// set depth attachment, `T` must be `GLRenderBuffer` or `GLTexture`
132     void setDepth(T)( T buf )
133     in{ assert( buf !is null ); } body
134     {
135         static if( is( T : GLRenderBuffer ) ) setRBODepth( buf );
136         else static if( is( T : GLTexture ) ) setTexDepth( buf );
137         else static assert(0, "only GLRenderBuffer or GLTexture can be setted as attachment" );
138     }
139 
140     /// set color attachment, `T` must be `GLRenderBuffer` or `GLTexture`
141     void setColor(T)( T buf, uint no=0 )
142     in{ assert( buf !is null ); } body
143     {
144         static if( is( T : GLRenderBuffer ) ) setRBOColor( buf, no );
145         else static if( is( T : GLTexture ) ) setTexColor( buf, no );
146         else static assert(0, "only GLRenderBuffer or GLTexture can be setted as attachment" );
147     }
148 
149     /// `glCheckFramebufferStatus`
150     void check()
151     {
152         bind(); scope(exit) unbind();
153         auto status = checkGLCall!glCheckFramebufferStatus( GL_FRAMEBUFFER );
154         import std..string;
155         if( status != GL_FRAMEBUFFER_COMPLETE )
156             throw new GLFBOException( format( "status isn't GL_FRAMEBUFFER_COMPLETE, it's %#x", status ) );
157         logger.Debug( "pass" );
158     }
159 
160 protected:
161 
162     /// warning: no bind
163     void setRBO( GLRenderBuffer rbo, GLenum attachment )
164     in{ assert( rbo !is null ); } body
165     {
166         checkGLCall!glFramebufferRenderbuffer( GL_FRAMEBUFFER,
167                             attachment, GL_RENDERBUFFER, rbo.id );
168     }
169 
170     /// warning: no bind
171     void texture1D( GLTexture tex, GLenum attachment, uint level=0 )
172     in { assert( tex !is null ); } body
173     {
174         checkGLCall!glFramebufferTexture1D( GL_FRAMEBUFFER, attachment,
175                                             tex.Target.T1D, tex.id, level );
176     }
177 
178     /// warning: no bind
179     void texture2D( GLTexture tex, GLenum attachment,
180                     GLTexture.Target target=GLTexture.Target.T2D, uint level=0 )
181     in { assert( tex !is null ); } body
182     {
183         checkGLCall!glFramebufferTexture2D( GL_FRAMEBUFFER, attachment,
184                                             target, tex.id, level );
185     }
186 
187     /// warning: no bind
188     void texture3D( GLTexture tex, GLenum attachment, uint level=0, int layer=0 )
189     in { assert( tex !is null ); } body
190     {
191         checkGLCall!glFramebufferTexture3D( GL_FRAMEBUFFER, attachment,
192                                             tex.Target.T3D, tex.id, level, layer );
193     }
194 
195     /// warning: no bind
196     void setTex( GLTexture tex, GLenum attachment, GLTexture.Target target=GLTexture.Target.T2D )
197     {
198         if( tex.target == tex.Target.T1D )
199             texture1D( tex, attachment );
200         else if( tex.target == tex.Target.T3D )
201             texture3D( tex, attachment );
202         else
203             texture2D( tex, attachment, target );
204     }
205 
206     override void selfDestroy()
207     {
208         unbind();
209         checkGLCall!glDeleteFramebuffers( 1, &_id );
210         logger.Debug( "pass" );
211     }
212 }