summaryrefslogtreecommitdiff
path: root/src/core/device.c
diff options
context:
space:
mode:
authorchai <chaifix@163.com>2019-12-21 22:24:15 +0800
committerchai <chaifix@163.com>2019-12-21 22:24:15 +0800
commitec111247c614663d8231245a17c314b9b8b4a28c (patch)
treea66058508161da488371c90316865ae850b8be15 /src/core/device.c
parentc3f45735ecfab6e567be371758f21395e92dfef6 (diff)
*misc
Diffstat (limited to 'src/core/device.c')
-rw-r--r--src/core/device.c396
1 files changed, 289 insertions, 107 deletions
diff --git a/src/core/device.c b/src/core/device.c
index b5468cd..dc4d80e 100644
--- a/src/core/device.c
+++ b/src/core/device.c
@@ -3,93 +3,111 @@
#include "clip.h"
#include "shader.h"
#include "../util/assert.h"
+#include "framebuffer.h"
+#include "depth.h"
+#include "stencil.h"
ssr_Config config;
-typedef enum ssr_VertexAttrMask {
+typedef enum {
VERTEXATTR_POS = 1,
VERTEXATTR_COLOR = 1 << 1,
VERTEXATTR_UV = 1 << 2,
VERTEXATTR_NORMAL = 1 << 3,
} ssr_VertexAttrMask;
-typedef struct VertexAttr {
+typedef struct {
int start;
int offset;
} VertexAttr;
-/*
-** 状态结构
-*/
static struct {
- /* MVP矩阵栈 */
Mat4 matrices[3][MATRIXDEPTH];
- uint matrixtop[3];
- ssr_MatrixMode matrixmode;
+ uint matrix_top[3];
+ ssr_MatrixMode matrix_mode;
+
+ Vec4 viewport;
- /* 屏幕缓冲区 */
Color* target;
- Color* framebuffer;
- uint buffersize; /*framebuffer size in bytess*/
+ Color* color_buffer; /*screen color buffer*/
+ uint buffer_size;
+
+ float* depth_buffer;
+ float* depth_screen_buffer;
+ DepthFunc depth_func;
- /* zbuffer 深度会被映射到0~1的非线性区间*/
- float* zbuffer;
+ byte* stencil_buffer;
+ byte* stencil_screen_buffer;
+ byte stencil_read_mask;
+ uint stencil_ref;
+ byte stencil_write_mask;
+ StencilFunc stencil_func;
+ StencilOp stencil_failop, stencil_dpfailop, stencil_passop;
- /* 顶点数据 */
- Vert* verts; uint nverts;
- uint* indices; uint nprims;
+ Vert* verts; uint vert_count;
+ uint* indices; uint prim_count;
- /* shader program */
Program* program;
UniformCollection uniforms;
- /* texture relavent */
- FilterMode filtermode;
- WrapMode wrapmode;
-
- //FrameBuffer* framebuffer;
+ FrameBuffer* frame_buffer;
uint enable;
struct {
ssr_BlendFactor src;
ssr_BlendFactor dst;
- } blendfactor;
+ } blend_factor;
} state;
-/*
-** MVP矩阵栈
-*/
-#define MATRIXTOP state.matrixtop[state.matrixmode]
-#define MATRIX state.matrices[state.matrixmode][MATRIXTOP]
-#define MATRIXSTACK state.matrices[state.matrixmode]
+#define MATRIXTOP state.matrix_top[state.matrix_mode]
+#define MATRIX state.matrices[state.matrix_mode][MATRIXTOP]
+#define MATRIXSTACK state.matrices[state.matrix_mode]
-#define GETMATRIX(MODE) state.matrices[MODE][state.matrixtop[MODE]]
+#define GETMATRIX(MODE) state.matrices[MODE][state.matrix_top[MODE]]
-#define BUFFER (state.framebuffer)
+#define BUFFER (state.color_buffer)
+/*topleft*/
#define contains(x, y, l, r, t, b) (x >= l && x <= r && y <= b && y >= t)
void ssr_init(ssr_Config* conf) {
config = *conf;
- ssrM_zero(state.matrixtop, sizeof(state.matrixtop));
+ ssrM_zero(state.matrix_top, sizeof(state.matrix_top));
+
state.target = conf->target;
+
+ int size = config.width * config.height;
+
if (config.dbuffer) {
- state.framebuffer = ssrM_newvector(Color, config.width * config.height);
+ state.color_buffer = ssrM_newvector(Color, size);
}
else {
- state.framebuffer = conf->target;
+ state.color_buffer = conf->target;
}
- state.buffersize = sizeof(Color) * config.width * config.height;
- state.zbuffer = ssrM_newvector(float, config.width * config.height);
- //memset(state.zbuffer, 0xff, sizeof(uint)*config.width*config.height);
+ state.buffer_size = sizeof(Color) * size;
+
+ state.depth_screen_buffer = ssrM_newvector(float, size);
+ state.depth_buffer = state.depth_screen_buffer;
+ state.depth_func = depth_leuqal;
+
+ state.blend_factor.src = BLEND_SRC_ALPHA;
+ state.blend_factor.dst = BLEND_ONE_MINUS_SRC_ALPHA;
+
+ state.stencil_screen_buffer = ssrM_newvector(byte, size);
+ state.stencil_buffer = state.stencil_screen_buffer;
+ state.stencil_ref = 1;
+ state.stencil_read_mask = 1;
+ state.stencil_write_mask = 0;
+ state.stencil_func = stencil_equal;
+ state.stencil_failop = stencilop_keep;
+ state.stencil_dpfailop = stencilop_keep;
+ state.stencil_passop = stencilop_replace;
- state.filtermode = FILTERMODE_POINT;
- state.wrapmode = WRAPMODE_CLAMP;
+ state.frame_buffer = NULL;
- state.blendfactor.src = BLEND_SRC_ALPHA;
- state.blendfactor.dst = BLEND_ONE_MINUS_SRC_ALPHA;
+ ssr_viewport(0, config.width, config.height, 0);
}
float ssr_getaspect() {
@@ -104,44 +122,38 @@ int ssr_getframebufferh() {
return config.height;
}
-void ssr_setfiltermode(FilterMode filter_mode) {
- state.filtermode = filter_mode;
-}
-
-void ssr_setwrapmode(WrapMode wrap_mode) {
- state.wrapmode = wrap_mode;
-}
-
-FilterMode ssr_getfiltermode() {
- return state.filtermode;
-}
-
-WrapMode ssr_getwrapmode() {
- return state.wrapmode;
-}
-
void ssr_bindframebuffer(FrameBuffer* fbo) {
-
+ ssr_assert(fbo);
+ state.frame_buffer = fbo;
+ state.depth_buffer = fbo->depth_buffer;
+ state.stencil_buffer = fbo->stencil_buffer;
}
void ssr_unbindframebuffer() {
-
+ state.frame_buffer = NULL;
+ state.depth_buffer = state.depth_screen_buffer;
+ state.stencil_buffer = state.stencil_screen_buffer;
}
void ssr_setblendfunc(ssr_BlendFactor sfactor, ssr_BlendFactor dfactor) {
- state.blendfactor.src = sfactor;
- state.blendfactor.dst = dfactor;
+ state.blend_factor.src = sfactor;
+ state.blend_factor.dst = dfactor;
+}
+
+void ssrU_blendscreencolor(uint x, uint y, Color32* src) {
+ Color c = state.color_buffer[x + y * config.width];
+ Color32 dst; color_tocolor32(c, &dst);
+ ssr_blend(src, &dst, src);
}
void ssr_matrixmode(ssr_MatrixMode mode) {
- state.matrixmode = mode;
+ state.matrix_mode = mode;
}
void ssr_loadidentity() {
mat4_setidentity(&MATRIX);
}
-/* push进去之后会将最新的拷贝一份放在栈顶 */
void ssr_pushmatrix() {
ssr_assert(MATRIXTOP < MATRIXDEPTH - 1);
++MATRIXTOP;
@@ -152,7 +164,6 @@ void ssr_popmatrix() {
MATRIXTOP = clamp(--MATRIXTOP, 0, MATRIXDEPTH - 1);
}
-/*用来做world->view的变换矩阵*/
/*http://warmcat.org/chai/blog/?p=559*/
void ssr_lookat(Vec3* pos, Vec3* target, Vec3* up) {
ssr_assert(pos && target && up);
@@ -171,7 +182,6 @@ void ssr_lookat(Vec3* pos, Vec3* target, Vec3* up) {
mat4_multiply(&MATRIX, &m, &MATRIX);
}
-/* scalematrix * matrix */
void ssr_scale(float sx, float sy, float sz) {
Vec3 scale = { sx, sy, sz };
mat4_scale(&MATRIX, &scale, &MATRIX);
@@ -227,6 +237,10 @@ bool ssr_isenable(uint mask) {
}
void ssr_viewport(float l, float r, float b, float t) {
+ state.viewport.x = l;
+ state.viewport.y = r;
+ state.viewport.z = b;
+ state.viewport.w = t;
}
void ssr_ortho(float l, float r, float b, float t, float n, float f) {
@@ -236,13 +250,14 @@ void ssr_ortho(float l, float r, float b, float t, float n, float f) {
}
void ssr_frustum(float l, float r, float b, float t, float n, float f) {
+ ssr_assert(n > 0 && f > 0 && f > n);
Mat4 m;
mat4_setfrustum(l, r, b, t, n, f, &m);
mat4_multiply(&MATRIX, &m, &MATRIX);
}
-/*注意这里nf都要是正数,代表距离*/
void ssr_perspective(float fov, float aspect, float n, float f) {
+ ssr_assert(n > 0 && f > 0 && f > n);
Mat4 m;
mat4_setperspective(fov, aspect, n, f, &m);
mat4_multiply(&MATRIX, &m, &MATRIX);
@@ -250,30 +265,60 @@ void ssr_perspective(float fov, float aspect, float n, float f) {
void ssr_present() {
if (config.dbuffer) {
- memcpy(state.target, state.framebuffer, state.buffersize);
+ memcpy(state.target, state.color_buffer, state.buffer_size);
}
}
-void ssr_clearcolor(Color color) {
+static void _clearscreen(Color color) {
if (color == 0x00) {
- memset(state.framebuffer, 0, state.buffersize);
+ memset(state.color_buffer, 0, state.buffer_size);
}
else {
uint size = config.width * sizeof(Color);
- for(int x = 0; x < config.width; ++x)
- state.framebuffer[x] = color;
+ for (int x = 0; x < config.width; ++x)
+ state.color_buffer[x] = color;
for (int y = 1; y < config.height; ++y)
- ssrM_copy(&state.framebuffer[config.width * y], state.framebuffer, size);
+ ssrM_copy(&state.color_buffer[config.width * y], state.color_buffer, size);
+ }
+}
+
+static void _clearcolorbuffer(Color32 color) {
+ Texture* rt;
+ Color32* pixels;
+ for (int i = 0; i < RENDER_TARGET_COUNT; ++i) {
+ rt = state.frame_buffer->render_textures[i];
+ if (rt == NULL) continue;
+ pixels = rt->pixels;
+ uint size = rt->width * sizeof(Color32);
+ for (int x = 0; x < rt->width; ++x)
+ pixels[x] = color;
+ for (int y = 1; y < rt->height; ++y)
+ ssrM_copy(&pixels[rt->width * y], pixels, size);
+ }
+}
+
+void ssr_clearcolor(Color color) {
+ if (state.frame_buffer == NULL) {
+ _clearscreen(color);
+ }
+ else {
+ Color32 c; color_tocolor32(color, &c);
+ _clearcolorbuffer(c);
}
}
void ssr_cleardepth() {
- //memset(state.zbuffer, 0xff, sizeof(uint)*config.width*config.height);
+ ssr_assert(state.depth_buffer);
for (int i = 0; i < config.width * config.height; ++i) {
- state.zbuffer[i] = 1;
+ state.depth_buffer[i] = 1;
}
}
+void ssr_clearstencil(byte val) {
+ ssr_assert(state.stencil_buffer);
+ memset(state.stencil_buffer, val, config.width * config.height);
+}
+
void ssr_putpoint(uint screenx, uint screeny, Color color) {
if (!contains(screenx, screeny, 0, config.width - 1, 0, config.height - 1))
return;
@@ -291,26 +336,46 @@ Color ssr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char
}
bool ssr_testdepth(uint x, uint y, float depth){
+ ssr_assert(state.depth_buffer);
uint off = x + y * config.width;
- return state.zbuffer[off] >= depth;
+ return state.depth_func(depth, state.depth_buffer[off]);
}
void ssr_writedepth(uint x, uint y, float depth) {
+ ssr_assert(state.depth_buffer);
uint off = x + y * config.width;
- state.zbuffer[off] = depth;
+ state.depth_buffer[off] = depth;
+}
+
+void ssr_setdepthfunc(ssr_DepthFunc func) {
+ switch (func)
+ {
+ case DEPTHFUNC_ALWAYS: state.depth_func = depth_always; break;
+ case DEPTHFUNC_NEVER: state.depth_func = depth_never; break;
+ case DEPTHFUNC_LESS: state.depth_func = depth_less; break;
+ case DEPTHFUNC_EQUAL: state.depth_func = depth_equal; break;
+ case DEPTHFUNC_LEQUAL: state.depth_func = depth_leuqal; break;
+ case DEPTHFUNC_GREATER: state.depth_func = depth_greater; break;
+ case DEPTHFUNC_NOTEQUAL: state.depth_func = depth_notequal; break;
+ case DEPTHFUNC_GEQUAL: state.depth_func = depth_gequer; break;
+ default: ssr_assert(FALSE);
+ }
}
void ssrU_viewport(Vec2* p, Vec2* out) {
ssr_assert(p && out);
- float halfw = config.width / 2.f, halfh = config.height / 2.f;
- //out->x = (int)round(p->x * halfw + halfw);
- //out->y = (int)round(halfh - p->y * halfh);
- out->x = (int)round((p->x + 1) * (halfw - 0.5f));
- out->y = (int)round((1 - p->y) * (halfh - 0.5f));
+ float l = state.viewport.x,
+ r = state.viewport.y,
+ b = state.viewport.z,
+ t = state.viewport.w,
+ w2 = (r - l) * 0.5f,
+ h2 = (b - t) * 0.5f;
+ out->x = (int)round((p->x + 1) * (w2 - 0.5f) + l);
+ out->y = (int)round((1 - p->y) * (h2 - 0.5f) + t);
}
void ssr_blend(Color32* src, Color32* dst, Color32* out) {
- ssrU_blend(state.blendfactor.src, state.blendfactor.dst, src, dst, out);
+ ssrU_blend(state.blend_factor.src, state.blend_factor.dst, src, dst, out);
}
static _blend(ssr_BlendFactor factor, Color32* src, Color32* dst, Color32* out) {
@@ -391,18 +456,12 @@ void ssrU_blend(
out->a = src->a + dst->a;
}
-Color32 ssr_getfbocolor(uint x, uint y) {
- Color c = state.framebuffer[x + y * config.width];
- Color32 c32; color_tocolor32(c, &c32);
- return c32;
-}
-
-void ssr_bindvertices(Vert* verts, int nverts, uint* indices, int nprims) {
+void ssr_bindvertices(Vert* verts, int vert_count, uint* indices, int prim_count) {
ssr_assert(verts && indices);
state.verts = verts;
- state.nverts = nverts;
+ state.vert_count = vert_count;
state.indices = indices;
- state.nprims = nprims;
+ state.prim_count = prim_count;
}
void ssr_useprogram(Program* program) {
@@ -444,30 +503,31 @@ void ssr_draw(ssr_PrimitiveType primitive) {
/*prepare registers if necessary*/
if (use_extra_varyings) {
ssrS_openregs(varying_flag);
- ssrS_setregisters(state.nverts);
+ ssrS_setregisters(state.vert_count);
ssrS_setactiveregr();
}
/*resize clipping coords buffer*/
- if (clip_coords.length < state.nverts) {
+ if (clip_coords.length < state.vert_count) {
ssrM_resizevector(
Vec4,
clip_coords.coords,
clip_coords.length,
- state.nverts,
+ state.vert_count,
FALSE
);
}
/*vertex operation*/
- for (int i = 0; i < state.nverts; ++i) {
+ VertexShader vert_shader = state.program->vertexshader;
+ for (int i = 0; i < state.vert_count; ++i) {
Vert* vert = &state.verts[i];
ssr_vert_in.vertex = vert;
/*set register pointers*/
if (use_extra_varyings) {
ssrS_setupregisterpointers(vert->index);
}
- state.program->vertexshader(&state.uniforms, &ssr_vert_in, &clip_coords.coords[i]);
+ vert_shader(&state.uniforms, &ssr_vert_in, &clip_coords.coords[i]);
}
/*set register pointer to frag-in*/
@@ -479,9 +539,8 @@ void ssr_draw(ssr_PrimitiveType primitive) {
if (primitive == PRIMITIVE_TRIANGLE) {
uint i0, i1, i2;
Vec4 *h0, *h1, *h2;
- Vert *v0, *v1, *v2;
bool reset_active_reg = FALSE;
- for (int i = 0; i < state.nprims; ++i) {
+ for (int i = 0; i < state.prim_count; ++i) {
i0 = state.indices[i * 3];
i1 = state.indices[i * 3 + 1];
i2 = state.indices[i * 3 + 2];
@@ -490,10 +549,6 @@ void ssr_draw(ssr_PrimitiveType primitive) {
h1 = &clip_coords.coords[i1],
h2 = &clip_coords.coords[i2];
- v0 = &state.verts[i0];
- v1 = &state.verts[i1];
- v2 = &state.verts[i2];
-
/*back face culling*/
if (ssr_isenable(ENABLE_BACKFACECULL)) {
float w0 = 1 / h0->w, w1 = 1 / h1->w, w2 = 1 / h2->w;
@@ -508,7 +563,7 @@ void ssr_draw(ssr_PrimitiveType primitive) {
}
/*clipping*/
- bool clipped = clip_triangle(h0, h1, h2, v0, v1, v2, varying_flag, &clip_buffer);
+ bool clipped = clip_triangle(h0, h1, h2, i0, i1, i2, varying_flag, &clip_buffer);
/*rasterization*/
if (!clipped) {
@@ -516,7 +571,7 @@ void ssr_draw(ssr_PrimitiveType primitive) {
reset_active_reg = FALSE;
ssrS_setactiveregr();
}
- ssrR_triangle(h0, h1, h2, v0, v1, v2, state.program, &state.uniforms);
+ ssrR_triangle(h0, h1, h2, i0, i1, i2, state.program, &state.uniforms);
} else {
if (!reset_active_reg) {
reset_active_reg = TRUE;
@@ -528,14 +583,14 @@ void ssr_draw(ssr_PrimitiveType primitive) {
vt1 = &clip_buffer.vertices[i];
vt2 = &clip_buffer.vertices[i + 1];
h0 = &vt0->clip_coord; h1 = &vt1->clip_coord; h2 = &vt2->clip_coord;
- v0 = &vt0->vertex; v1 = &vt1->vertex; v2 = &vt2->vertex;
- ssrR_triangle(h0, h1, h2, v0, v1, v2, state.program, &state.uniforms);
+ i0 = vt0->index; i1 = vt1->index; i2 = vt2->index;
+ ssrR_triangle(h0, h1, h2, i0, i1, i2, state.program, &state.uniforms);
}
}
}
}
} else if (primitive == PRIMITIVE_POINT) {
-
+
} else if (primitive == PRIMITIVE_LINE) {
}
@@ -574,3 +629,130 @@ void ssr_setuniformtex(uint idx, Texture* tex) {
void ssr_setuniformu(void* userdata) {
state.uniforms.userdata = userdata;
}
+
+bool ssr_teststencil(uint x, uint y, bool pass_depth_test) {
+ int index = x + config.width * y;
+ byte mask = state.stencil_read_mask;
+ byte val = state.stencil_buffer[index];
+ return state.stencil_func(val, state.stencil_ref);
+}
+
+void ssr_writestencil(uint x, uint y, bool pass_depth_test, bool pass_stencil_test) {
+ byte write_mask = state.stencil_write_mask;
+ if (write_mask == 0) return;/*dont write*/
+ int index = x + config.width * y;
+ byte read_mask = state.stencil_read_mask;
+ byte val = state.stencil_buffer[index];
+ if (pass_stencil_test && pass_depth_test) {
+ val = state.stencil_passop(val & read_mask, state.stencil_ref & read_mask);
+ }
+ else if (pass_stencil_test && !pass_depth_test) {
+ val = state.stencil_dpfailop(val & read_mask, state.stencil_ref & read_mask);
+ }
+ else if (!pass_stencil_test) {
+ val = state.stencil_failop(val & read_mask, state.stencil_ref & read_mask);
+ }
+ /*https://stackoverflow.com/questions/31660747/what-exactly-does-glstencilmask-do*/
+ state.stencil_buffer[index] &= ~write_mask;
+ state.stencil_buffer[index] |= (val & write_mask);
+}
+
+void ssr_setstencilfunc(ssr_StencilFunc func, byte ref, uint mask) {
+ switch (func)
+ {
+ case STENCILFUNC_ALWAYS: state.stencil_func = stencil_always; break;
+ case STENCILFUNC_NEVER: state.stencil_func = stencil_never; break;
+ case STENCILFUNC_LESS: state.stencil_func = stencil_less; break;
+ case STENCILFUNC_EQUAL: state.stencil_func = stencil_equal; break;
+ case STENCILFUNC_LEQUAL: state.stencil_func = stencil_leuqal; break;
+ case STENCILFUNC_GREATER: state.stencil_func = stencil_greater; break;
+ case STENCILFUNC_NOTEQUAL: state.stencil_func = stencil_notequal; break;
+ case STENCILFUNC_GEQUAL: state.stencil_func = stencil_gequer; break;
+ default: ssr_assert(FALSE);
+ }
+ state.stencil_ref = ref;
+ state.stencil_read_mask = mask;
+}
+
+static void setstencilop(ssr_StencilOp v, StencilOp* op) {
+ switch (v) {
+ case STENCILOP_KEEP: *op = stencilop_keep; break;
+ case STENCILOP_ZERO: *op = stencilop_zero; break;
+ case STENCILOP_REPLACE: *op = stencilop_replace; break;
+ case STENCILOP_INCR: *op = stencilop_incr; break;
+ case STENCILOP_INCR_WRAP: *op = stencilop_incrwrap; break;
+ case STENCILOP_DECR: *op = stencilop_decr; break;
+ case STENCILOP_DECR_WRAP: *op = stencilop_decrwrap; break;
+ case STENCILOP_INVERT: *op = stencilop_invert; break;
+ default:
+ ssr_assert(FALSE);
+ }
+}
+
+void ssr_setstencilop(ssr_StencilOp fail, ssr_StencilOp dpfail, ssr_StencilOp pass) {
+ setstencilop(fail, &state.stencil_failop);
+ setstencilop(dpfail, &state.stencil_dpfailop);
+ setstencilop(pass, &state.stencil_passop);
+}
+
+void ssr_setstencilmask(byte mask) {
+ state.stencil_write_mask = mask;
+}
+
+bool ssr_iswritesencil() {
+ return state.stencil_write_mask != 0x00;
+}
+
+void ssr_blendandputpoint(int x, int y, bool blend) {
+ if (!state.frame_buffer) { /*directly onto screen*/
+ if (blend) {
+ ssrU_blendscreencolor(x, y, out_color[0]);
+ }
+ color32_saturate(out_color[0]);
+ ssr_putpoint32(x, y, out_color[0]);
+ }
+ else { /*onto framebuffer color buffer*/
+ Color32 *src, *dst;
+ Texture* rt;
+ for (int i = 0; i < RENDER_TARGET_COUNT; ++i) {
+ rt = state.frame_buffer->render_textures[i];
+ if (rt == NULL || !contains(x, y, 0, rt->width, 0, rt->height))
+ continue;
+ dst = &rt->pixels[x + y * rt->width];
+ src = out_color[i];
+ if (blend)
+ ssr_blend(src, dst, src);
+ *dst = *src;
+ }
+ }
+}
+
+FrameBuffer* fbo_create() {
+ FrameBuffer* fb = ssrM_new(FrameBuffer);
+ fbo_attachdepthbuffer(fb, 0);
+ fbo_attachstencilbuffer(fb, 0);
+ ssrM_zero(fb->render_textures, sizeof(Texture*) * RENDER_TARGET_COUNT);
+ return fb;
+}
+
+void fbo_attachrendertexture(FrameBuffer* fb, uint attachment, Texture* rt) {
+ fb->render_textures[attachment] = rt;
+}
+
+void fbo_attachdepthbuffer(FrameBuffer* fb, float* depthbuffer) {
+ if (depthbuffer == NULL)
+ fb->depth_buffer = state.depth_screen_buffer;
+ else
+ fb->depth_buffer = depthbuffer;
+}
+
+void fbo_attachstencilbuffer(FrameBuffer* fb, byte* stencilbuffer) {
+ if (stencilbuffer == NULL)
+ fb->stencil_buffer = state.stencil_screen_buffer;
+ else
+ fb->stencil_buffer = stencilbuffer;
+}
+
+void fbo_free(FrameBuffer* fb) {
+ ssrM_free(fb);
+}