/************************************************************/ /** * * @file: main.cpp * @author: Martin Fouilleul * @date: 27/02/2022 * @revision: * *****************************************************************/ #include "glsl_shaders.h" #include "math.h" #include "orca.h" //---------------------------------------------------------------- //NOTE(martin): GL vertex struct and identifiers //---------------------------------------------------------------- typedef struct Vertex { float x, y; } Vertex; typedef struct advect_program { GLuint prog; GLint pos; GLint src; GLint velocity; GLint delta; GLint dissipation; } advect_program; typedef struct div_program { GLuint prog; GLint pos; GLint src; } div_program; typedef struct jacobi_program { GLuint prog; GLint pos; GLint xTex; GLint bTex; } jacobi_program; typedef struct blit_residue_program { GLuint prog; GLint pos; GLint mvp; GLint xTex; GLint bTex; } blit_residue_program; typedef struct multigrid_restrict_residual_program { GLuint prog; GLint pos; GLint xTex; GLint bTex; } multigrid_restrict_residual_program; typedef struct multigrid_correct_program { GLuint prog; GLint pos; GLint src; GLint error; GLint invGridSize; } multigrid_correct_program; typedef struct subtract_program { GLuint prog; GLint pos; GLint src; GLint pressure; GLint invGridSize; } subtract_program; typedef struct blit_program { GLuint prog; GLint pos; GLint mvp; GLint gridSize; GLint tex; } blit_program; typedef struct splat_program { GLuint prog; GLint pos; GLint src; GLint splatPos; GLint splatColor; GLint radius; GLint additive; GLint blending; GLint randomize; } splat_program; typedef struct frame_buffer { GLuint textures[2]; GLuint fbos[2]; } frame_buffer; advect_program advectProgram; div_program divProgram; jacobi_program jacobiProgram; multigrid_restrict_residual_program multigridRestrictResidualProgram; multigrid_correct_program multigridCorrectProgram; subtract_program subtractProgram; splat_program splatProgram; blit_program blitProgram; blit_program blitDivProgram; blit_residue_program blitResidueProgram; frame_buffer colorBuffer; frame_buffer velocityBuffer; const int MULTIGRID_COUNT = 4; frame_buffer pressureBuffer[4]; frame_buffer divBuffer[4]; GLuint vertexBuffer; //---------------------------------------------------------------- //NOTE(martin): initialization //---------------------------------------------------------------- GLuint compile_shader(const char* vs, const char* fs) { GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertexShader, 1, &vs, 0); glCompileShader(vertexShader); GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragmentShader, 1, &fs, 0); glCompileShader(fragmentShader); GLuint prog = glCreateProgram(); glAttachShader(prog, vertexShader); glAttachShader(prog, fragmentShader); glLinkProgram(prog); //TODO errors int status = 0; glGetProgramiv(prog, GL_LINK_STATUS, &status); if(status != GL_TRUE) { oc_log_error("program failed to link: "); int logSize = 0; glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logSize); oc_arena_scope scratch = oc_scratch_begin(); char* log = oc_arena_push(scratch.arena, logSize); glGetProgramInfoLog(prog, logSize, 0, log); oc_log_error("%s\n", log); oc_scratch_end(scratch); } int err = glGetError(); if(err) { oc_log_error("gl error %i\n", err); } return (prog); } void init_advect(advect_program* program) { oc_log_info("compiling advect..."); program->prog = compile_shader(glsl_common_vertex, glsl_advect); program->pos = glGetAttribLocation(program->prog, "pos"); program->src = glGetUniformLocation(program->prog, "src"); program->velocity = glGetUniformLocation(program->prog, "velocity"); program->delta = glGetUniformLocation(program->prog, "delta"); program->dissipation = glGetUniformLocation(program->prog, "dissipation"); } void init_div(div_program* program) { oc_log_info("compiling div..."); program->prog = compile_shader(glsl_common_vertex, glsl_divergence); program->pos = glGetAttribLocation(program->prog, "pos"); program->src = glGetUniformLocation(program->prog, "src"); } void init_jacobi(jacobi_program* program) { oc_log_info("compiling jacobi..."); program->prog = compile_shader(glsl_common_vertex, glsl_jacobi_step); program->pos = glGetAttribLocation(program->prog, "pos"); program->xTex = glGetUniformLocation(program->prog, "xTex"); program->bTex = glGetUniformLocation(program->prog, "bTex"); } void init_multigrid_restrict_residual(multigrid_restrict_residual_program* program) { oc_log_info("compiling multigrid restrict residual..."); program->prog = compile_shader(glsl_common_vertex, glsl_multigrid_restrict_residual); program->pos = glGetAttribLocation(program->prog, "pos"); program->xTex = glGetUniformLocation(program->prog, "xTex"); program->bTex = glGetUniformLocation(program->prog, "bTex"); } void init_multigrid_correct(multigrid_correct_program* program) { oc_log_info("compiling multigrid correct..."); program->prog = compile_shader(glsl_common_vertex, glsl_multigrid_correct); program->pos = glGetAttribLocation(program->prog, "pos"); program->src = glGetUniformLocation(program->prog, "src"); program->error = glGetUniformLocation(program->prog, "error"); program->invGridSize = glGetUniformLocation(program->prog, "invGridSize"); } void init_subtract(subtract_program* program) { oc_log_info("compiling subtract..."); program->prog = compile_shader(glsl_common_vertex, glsl_subtract_pressure); program->pos = glGetAttribLocation(program->prog, "pos"); program->src = glGetUniformLocation(program->prog, "src"); program->pressure = glGetUniformLocation(program->prog, "pressure"); program->invGridSize = glGetUniformLocation(program->prog, "invGridSize"); } void init_splat(splat_program* program) { oc_log_info("compiling splat..."); program->prog = compile_shader(glsl_common_vertex, glsl_splat); program->pos = glGetAttribLocation(program->prog, "pos"); program->src = glGetUniformLocation(program->prog, "src"); program->splatPos = glGetUniformLocation(program->prog, "splatPos"); program->splatColor = glGetUniformLocation(program->prog, "splatColor"); program->radius = glGetUniformLocation(program->prog, "radius"); program->additive = glGetUniformLocation(program->prog, "additive"); program->blending = glGetUniformLocation(program->prog, "blending"); program->randomize = glGetUniformLocation(program->prog, "randomize"); } void init_blit(blit_program* program) { oc_log_info("compiling blit..."); program->prog = compile_shader(glsl_blit_vertex, glsl_blit_fragment); program->pos = glGetAttribLocation(program->prog, "pos"); program->mvp = glGetUniformLocation(program->prog, "mvp"); program->tex = glGetUniformLocation(program->prog, "tex"); program->gridSize = glGetUniformLocation(program->prog, "gridSize"); } void init_blit_div(blit_program* program) { oc_log_info("compiling blit div..."); program->prog = compile_shader(glsl_blit_div_vertex, glsl_blit_div_fragment); program->pos = glGetAttribLocation(program->prog, "pos"); program->mvp = glGetUniformLocation(program->prog, "mvp"); program->tex = glGetUniformLocation(program->prog, "tex"); } void init_blit_residue(blit_residue_program* program) { oc_log_info("compiling blit residue..."); program->prog = compile_shader(glsl_blit_div_vertex, glsl_blit_residue_fragment); program->pos = glGetAttribLocation(program->prog, "pos"); program->mvp = glGetUniformLocation(program->prog, "mvp"); program->xTex = glGetUniformLocation(program->prog, "xTex"); program->bTex = glGetUniformLocation(program->prog, "bTex"); } GLuint create_texture(int width, int height, GLenum internalFormat, GLenum format, GLenum type, char* initData) { GLuint texture; glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, type, initData); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); return (texture); } GLuint create_fbo(GLuint texture) { GLuint fbo; glGenFramebuffers(1, &fbo); glBindFramebuffer(GL_FRAMEBUFFER, fbo); glBindTexture(GL_TEXTURE_2D, texture); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); return (fbo); } void init_frame_buffer(frame_buffer* framebuffer, int width, int height, GLenum internalFormat, GLenum format, GLenum type, char* initData) { for(int i = 0; i < 2; i++) { framebuffer->textures[i] = create_texture(width, height, internalFormat, format, type, initData); framebuffer->fbos[i] = create_fbo(framebuffer->textures[i]); } GLenum err = glCheckFramebufferStatus(GL_FRAMEBUFFER); if(err != GL_FRAMEBUFFER_COMPLETE) { oc_log_info("Frame buffer incomplete, %i", err); } } void frame_buffer_swap(frame_buffer* buffer) { GLuint tmp = buffer->fbos[0]; buffer->fbos[0] = buffer->fbos[1]; buffer->fbos[1] = tmp; tmp = buffer->textures[0]; buffer->textures[0] = buffer->textures[1]; buffer->textures[1] = tmp; } //---------------------------------------------------------------- //NOTE(martin): entry point //---------------------------------------------------------------- #define texWidth (256) #define texHeight (256) float colorInitData[texWidth][texHeight][4] = { 0 }; float velocityInitData[texWidth][texHeight][4] = { 0 }; const float EPSILON = 1., INV_GRID_SIZE = 1. / (float)texWidth, DELTA = 1. / 120.; const GLenum TEX_INTERNAL_FORMAT = GL_RGBA32F; const GLenum TEX_FORMAT = GL_RGBA; const GLenum TEX_TYPE = GL_FLOAT; #define square(x) ((x) * (x)) /* void reset_texture(GLuint texture, float width, float height, char* initData) { glBindTexture(GL_TEXTURE_2D, texture); glTexImage2D(GL_TEXTURE_2D, 0, TEX_INTERNAL_FORMAT, width, height, 0, TEX_FORMAT, TEX_TYPE, initData); } static bool resetCmd = false; void reset() { // resetCmd = true; oc_log_info("reset"); reset_texture(colorBuffer.textures[0], texWidth, texHeight, (char*)colorInitData); reset_texture(colorBuffer.textures[1], texWidth, texHeight, (char*)colorInitData); reset_texture(velocityBuffer.textures[0], texWidth, texHeight, (char*)velocityInitData); reset_texture(velocityBuffer.textures[1], texWidth, texHeight, (char*)velocityInitData); int gridFactor = 1; for(int i=0; ifbos[1]); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, x->textures[0]); glUniform1i(jacobiProgram.xTex, 0); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, b->textures[0]); glUniform1i(jacobiProgram.bTex, 1); glDrawArrays(GL_TRIANGLES, 0, 6); frame_buffer_swap(x); } } void multigrid_coarsen_residual(frame_buffer* output, frame_buffer* x, frame_buffer* b, float invFineGridSize) { //NOTE: compute residual and downsample to coarser grid, put result in coarser buffer glUseProgram(multigridRestrictResidualProgram.prog); glBindFramebuffer(GL_FRAMEBUFFER, output->fbos[1]); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, x->textures[0]); glUniform1i(multigridRestrictResidualProgram.xTex, 0); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, b->textures[0]); glUniform1i(multigridRestrictResidualProgram.bTex, 1); glDrawArrays(GL_TRIANGLES, 0, 6); frame_buffer_swap(output); } void multigrid_prolongate_and_correct(frame_buffer* x, frame_buffer* error, float invFineGridSize) { //NOTE: correct finer pressure glUseProgram(multigridCorrectProgram.prog); glBindFramebuffer(GL_FRAMEBUFFER, x->fbos[1]); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, x->textures[0]); glUniform1i(multigridCorrectProgram.src, 0); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, error->textures[0]); glUniform1i(multigridCorrectProgram.error, 1); glUniform1f(multigridCorrectProgram.invGridSize, invFineGridSize); glDrawArrays(GL_TRIANGLES, 0, 6); frame_buffer_swap(x); } void multigrid_clear(frame_buffer* error) { glBindFramebuffer(GL_FRAMEBUFFER, error->fbos[0]); glClear(GL_COLOR_BUFFER_BIT); } void input_splat(float t) { //NOTE: apply force and dye if(mouseInput.down && (mouseInput.deltaX || mouseInput.deltaY)) { // account for margin float margin = 32; float offset = margin / texWidth; float ratio = 1 - 2 * margin / texWidth; float splatPosX = (mouseInput.x / frameWidth) * ratio + offset; float splatPosY = (1 - mouseInput.y / frameHeight) * ratio + offset; float splatVelX = (10000. * DELTA * mouseInput.deltaX / frameWidth) * ratio; float splatVelY = (-10000. * DELTA * mouseInput.deltaY / frameWidth) * ratio; float intensity = 100 * sqrtf(square(ratio * mouseInput.deltaX / frameWidth) + square(ratio * mouseInput.deltaY / frameHeight)); float r = intensity * (sinf(2 * M_PI * 0.1 * t) + 1); float g = 0.5 * intensity * (cosf(2 * M_PI * 0.1 / M_E * t + 654) + 1); float b = intensity * (sinf(2 * M_PI * 0.1 / M_SQRT2 * t + 937) + 1); float radius = 0.005; apply_splat(splatPosX, splatPosY, radius, splatVelX, splatVelY, r, g, b, false); mouseInput.deltaX = 0; mouseInput.deltaY = 0; } } float testDiv[texWidth / 2][texWidth / 2][4]; oc_surface surface; ORCA_EXPORT void oc_on_init() { oc_log_info("Hello, world (from C)"); oc_runtime_window_set_title(OC_STR8("fluid")); surface = oc_surface_gles(); oc_surface_select(surface); // init_color_checker(); // init_velocity_vortex(); // init programs init_advect(&advectProgram); init_div(&divProgram); init_jacobi(&jacobiProgram); init_multigrid_restrict_residual(&multigridRestrictResidualProgram); init_multigrid_correct(&multigridCorrectProgram); init_blit_residue(&blitResidueProgram); init_subtract(&subtractProgram); init_splat(&splatProgram); init_blit(&blitProgram); init_blit_div(&blitDivProgram); // init frame buffers oc_log_info("create color buffer"); init_frame_buffer(&colorBuffer, texWidth, texHeight, TEX_INTERNAL_FORMAT, TEX_FORMAT, TEX_TYPE, (char*)colorInitData); oc_log_info("create velocity buffer"); init_frame_buffer(&velocityBuffer, texWidth, texHeight, TEX_INTERNAL_FORMAT, TEX_FORMAT, TEX_TYPE, (char*)velocityInitData); int gridFactor = 1; for(int i = 0; i < MULTIGRID_COUNT; i++) { oc_log_info("create div buffer %i", i); init_frame_buffer(&divBuffer[i], texWidth / gridFactor, texHeight / gridFactor, TEX_INTERNAL_FORMAT, TEX_FORMAT, TEX_TYPE, 0); oc_log_info("create pressure buffer %i", i); init_frame_buffer(&pressureBuffer[i], texWidth / gridFactor, texHeight / gridFactor, TEX_INTERNAL_FORMAT, TEX_FORMAT, TEX_TYPE, 0); gridFactor *= 2; } // init vertex buffer static Vertex vertices[6] = { { -1, -1 }, { 1, -1 }, { 1, 1 }, { -1, -1 }, { 1, 1 }, { -1, 1 } }; //WARN: we assume blitProgram.pos == advectProgram.pos, is there a situation where it wouldn't be true?? GLuint vertexBuffer = 0; glGenBuffers(1, &vertexBuffer); glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); glBufferData(GL_ARRAY_BUFFER, 6 * sizeof(Vertex), vertices, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(blitProgram.pos, 2, GL_FLOAT, GL_FALSE, 0, 0); for(int i = 0; i < texWidth / 2; i++) { for(int j = 0; j < texHeight / 2; j++) { testDiv[i][j][0] = 0.5 + 0.5 * cosf(j / 100. * 3.14159 + i / 100. * 1.2139); } } } ORCA_EXPORT void oc_on_resize(u32 width, u32 height) { frameWidth = width * 2; frameHeight = height * 2; } ORCA_EXPORT void oc_on_frame_refresh() { float aspectRatio = texWidth / texHeight; //TODO replace with actual aspect ratio? static float t = 0; t += 1. / 60.; oc_surface_select(surface); glViewport(0, 0, texWidth, texHeight); //NOTE: advect velocity thru itself glUseProgram(advectProgram.prog); glBindFramebuffer(GL_FRAMEBUFFER, velocityBuffer.fbos[1]); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, velocityBuffer.textures[0]); glUniform1i(advectProgram.src, 0); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, velocityBuffer.textures[0]); glUniform1i(advectProgram.velocity, 1); glUniform1f(advectProgram.delta, DELTA); glUniform1f(advectProgram.dissipation, 0.01); glDrawArrays(GL_TRIANGLES, 0, 6); frame_buffer_swap(&velocityBuffer); /* //DEBUG static bool splatTrig = false; static bool splat = false; static float splatStart = 0; static int splatDir = 0; static int frameCount = 0; if(resetCmd) { frameCount = 0; splat = true; splatStart = frameT; } if(splat) { if(frameT - splatStart >= 0.5) { splat = false; splatDir++; splatDir = splatDir % 3; } float dirX = 0; float dirY = 0; if(splatDir == 0) { dirX = 0; dirY = 0.3; } if(splatDir == 1) { dirX = 0.3; dirY = 0; } if(splatDir == 2) { dirX = 0.2121; dirY = 0.2121; } apply_splat(0.5, 0.5, dirX, dirY, 1.5, 1., 0.1, false); } resetCmd = false; if(frameCount>20) { return; } frameCount++; */ input_splat(t); //NOTE: compute divergence of advected velocity glUseProgram(divProgram.prog); glBindFramebuffer(GL_FRAMEBUFFER, divBuffer[0].fbos[1]); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, velocityBuffer.textures[0]); glUniform1i(divProgram.src, 0); glDrawArrays(GL_TRIANGLES, 0, 6); frame_buffer_swap(&divBuffer[0]); //NOTE: compute pressure glBindFramebuffer(GL_FRAMEBUFFER, pressureBuffer[0].fbos[1]); glClear(GL_COLOR_BUFFER_BIT); #if 0 multigrid_clear(&pressureBuffer[0]); jacobi_solve(&pressureBuffer[0], &divBuffer[0], INV_GRID_SIZE, texWidth*texHeight); #else multigrid_clear(&pressureBuffer[0]); for(int i = 0; i < 1; i++) { jacobi_solve(&pressureBuffer[0], &divBuffer[0], INV_GRID_SIZE, 2); multigrid_coarsen_residual(&divBuffer[1], &pressureBuffer[0], &divBuffer[0], INV_GRID_SIZE); multigrid_clear(&pressureBuffer[1]); jacobi_solve(&pressureBuffer[1], &divBuffer[1], 2 * INV_GRID_SIZE, 2); multigrid_coarsen_residual(&divBuffer[2], &pressureBuffer[1], &divBuffer[1], 2 * INV_GRID_SIZE); multigrid_clear(&pressureBuffer[2]); jacobi_solve(&pressureBuffer[2], &divBuffer[2], 4 * INV_GRID_SIZE, 30); multigrid_prolongate_and_correct(&pressureBuffer[1], &pressureBuffer[2], 2 * INV_GRID_SIZE); jacobi_solve(&pressureBuffer[1], &divBuffer[1], 2 * INV_GRID_SIZE, 8); multigrid_prolongate_and_correct(&pressureBuffer[0], &pressureBuffer[1], INV_GRID_SIZE); jacobi_solve(&pressureBuffer[0], &divBuffer[0], INV_GRID_SIZE, 4); } #endif //NOTE: subtract pressure gradient to advected velocity glUseProgram(subtractProgram.prog); glBindFramebuffer(GL_FRAMEBUFFER, velocityBuffer.fbos[1]); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, velocityBuffer.textures[0]); glUniform1i(subtractProgram.src, 0); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, pressureBuffer[0].textures[0]); glUniform1i(subtractProgram.pressure, 1); glUniform1f(subtractProgram.invGridSize, INV_GRID_SIZE); glDrawArrays(GL_TRIANGLES, 0, 6); frame_buffer_swap(&velocityBuffer); //NOTE: Advect color through corrected velocity field glUseProgram(advectProgram.prog); glBindFramebuffer(GL_FRAMEBUFFER, colorBuffer.fbos[1]); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, colorBuffer.textures[0]); glUniform1i(advectProgram.src, 0); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, velocityBuffer.textures[0]); glUniform1i(advectProgram.velocity, 1); glUniform1f(advectProgram.delta, DELTA); glUniform1f(advectProgram.dissipation, 0.001); glDrawArrays(GL_TRIANGLES, 0, 6); frame_buffer_swap(&colorBuffer); //NOTE: Blit color texture to screen //NOTE: blit residue to screen glViewport(0, 0, frameWidth, frameHeight); float displayMatrix[16] = { 1 / aspectRatio, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 }; /* glUseProgram(blitResidueProgram.prog); glBindFramebuffer(GL_FRAMEBUFFER, 0); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, pressureBuffer[0].textures[0]); glUniform1i(blitResidueProgram.xTex, 0); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, divBuffer[0].textures[0]); glUniform1i(blitResidueProgram.bTex, 1); glUniformMatrix4fv(blitResidueProgram.mvp, 1, GL_FALSE, displayMatrix); glDrawArrays(GL_TRIANGLES, 0, 6); //*/ //* glUseProgram(blitProgram.prog); glBindFramebuffer(GL_FRAMEBUFFER, 0); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, colorBuffer.textures[0]); glUniform1i(blitProgram.tex, 0); glUniform2i(blitProgram.gridSize, texWidth, texHeight); glUniformMatrix4fv(blitProgram.mvp, 1, GL_FALSE, displayMatrix); glDrawArrays(GL_TRIANGLES, 0, 6); /*/ //NOTE: recompute divergence of (corrected) velocity glUseProgram(divProgram.prog); glBindFramebuffer(GL_FRAMEBUFFER, divBuffer[0].fbos[1]); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, velocityBuffer.textures[0]); glUniform1i(divProgram.src, 0); glDrawArrays(GL_TRIANGLES, 0, 6); frame_buffer_swap(&divBuffer[0]); //NOTE: Blit divergence to screen glViewport(0, 0, canvas_width(), canvas_height()); glUseProgram(blitDivProgram.prog); glBindFramebuffer(GL_FRAMEBUFFER, 0); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, divBuffer[0].textures[0]); glUniform1i(blitDivProgram.tex, 0); glUniformMatrix4fv(blitDivProgram.mvp, 1, GL_FALSE, displayMatrix); glDrawArrays(GL_TRIANGLES, 0, 6); //*/ oc_surface_present(surface); }