+ struct framebuffer_attachment *at = &fb->attachments[attachment];
+
+ if( at->purpose != k_framebuffer_attachment_type_colour )
+ {
+ vg_fatal_exit_loop( "illegal operation: bind non-colour framebuffer"
+ " attachment to texture slot" );
+ }
+
+ glActiveTexture( GL_TEXTURE0 + slot );
+ glBindTexture( GL_TEXTURE_2D, fb->attachments[attachment].id );
+}
+
+
+/*
+ * Shaders
+ */
+VG_STATIC void shader_link_standard_ub( GLuint shader, int texture_id )
+{
+ GLuint idx = glGetUniformBlockIndex( shader, "ub_world_lighting" );
+ glUniformBlockBinding( shader, idx, 0 );
+
+ render_fb_bind_texture( gpipeline.fb_heightmap, 0, texture_id );
+ glUniform1i( glGetUniformLocation( shader, "g_world_depth" ), texture_id );
+}
+
+VG_STATIC void render_update_lighting_ub(void)
+{
+ struct ub_world_lighting *winf = &gpipeline.ub_world_lighting;
+ int c = 0;
+
+ for( int i=0; i<3; i++ )
+ {
+ struct light_widget *lw = &gpipeline.widgets[i];
+
+ if( lw->enabled )
+ {
+ float pitch = lw->dir[0],
+ yaw = lw->dir[1],
+ xz = cosf( pitch );
+
+ v3_copy( (v3f){ xz*cosf(yaw), sinf(pitch), xz*sinf(yaw) },
+ winf->g_light_directions[c] );
+ v3_copy( lw->colour, winf->g_light_colours[c] );
+
+ c ++;
+ }
+ }
+
+ winf->g_light_count = c;
+ winf->g_light_directions[0][3] = gpipeline.shadow_length;
+ winf->g_light_colours[0][3] = gpipeline.shadow_spread;
+
+ if( vg.quality_profile == k_quality_profile_low )
+ winf->g_shadow_samples = 0;
+ else
+ winf->g_shadow_samples = 8;
+
+ glBindBuffer( GL_UNIFORM_BUFFER, gpipeline.ubo_world_lighting );
+ glBufferSubData( GL_UNIFORM_BUFFER, 0, sizeof(struct ub_world_lighting),
+ &gpipeline.ub_world_lighting );
+}
+
+#define FB_FORMAT_STR( E ) { E, #E },
+
+/*
+ * Convert OpenGL attachment ID enum to string
+ */
+VG_STATIC const char *render_fb_attachment_str( GLenum e )
+{
+ struct { GLenum e; const char *str; }
+ formats[] =
+ {
+ FB_FORMAT_STR(GL_COLOR_ATTACHMENT0)
+ FB_FORMAT_STR(GL_COLOR_ATTACHMENT1)
+ FB_FORMAT_STR(GL_COLOR_ATTACHMENT2)
+ FB_FORMAT_STR(GL_COLOR_ATTACHMENT3)
+ FB_FORMAT_STR(GL_COLOR_ATTACHMENT4)
+ FB_FORMAT_STR(GL_DEPTH_STENCIL_ATTACHMENT)
+ };
+
+ for( int i=0; i<vg_list_size(formats); i++ )
+ if( formats[i].e == e )
+ return formats[i].str;
+
+ return "UNDEFINED";
+}
+
+/*
+ * Convert OpenGL texture format enums from TexImage2D table 1,2 &
+ * RenderBufferStorage Table 1, into strings
+ */
+VG_STATIC const char *render_fb_format_str( GLenum format )
+{
+ struct { GLenum e; const char *str; }
+ formats[] =
+ {
+ /* Table 1 */
+ FB_FORMAT_STR(GL_DEPTH_COMPONENT)
+ FB_FORMAT_STR(GL_DEPTH_STENCIL)
+ FB_FORMAT_STR(GL_RED)
+ FB_FORMAT_STR(GL_RG)
+ FB_FORMAT_STR(GL_RGB)
+ FB_FORMAT_STR(GL_RGBA)
+
+ /* Render buffer formats */
+ FB_FORMAT_STR(GL_DEPTH_COMPONENT16)
+ FB_FORMAT_STR(GL_DEPTH_COMPONENT24)
+ FB_FORMAT_STR(GL_DEPTH_COMPONENT32F)
+ FB_FORMAT_STR(GL_DEPTH24_STENCIL8)
+ FB_FORMAT_STR(GL_DEPTH32F_STENCIL8)
+ FB_FORMAT_STR(GL_STENCIL_INDEX8)
+
+ /* Table 2 */
+ FB_FORMAT_STR(GL_R8)
+ FB_FORMAT_STR(GL_R8_SNORM)
+ FB_FORMAT_STR(GL_R16)
+ FB_FORMAT_STR(GL_R16_SNORM)
+ FB_FORMAT_STR(GL_RG8)
+ FB_FORMAT_STR(GL_RG8_SNORM)
+ FB_FORMAT_STR(GL_RG16)
+ FB_FORMAT_STR(GL_RG16_SNORM)
+ FB_FORMAT_STR(GL_R3_G3_B2)
+ FB_FORMAT_STR(GL_RGB4)
+ FB_FORMAT_STR(GL_RGB5)
+ FB_FORMAT_STR(GL_RGB8)
+ FB_FORMAT_STR(GL_RGB8_SNORM)
+ FB_FORMAT_STR(GL_RGB10)
+ FB_FORMAT_STR(GL_RGB12)
+ FB_FORMAT_STR(GL_RGB16_SNORM)
+ FB_FORMAT_STR(GL_RGBA2)
+ FB_FORMAT_STR(GL_RGBA4)
+ FB_FORMAT_STR(GL_RGB5_A1)
+ FB_FORMAT_STR(GL_RGBA8)
+ FB_FORMAT_STR(GL_RGBA8_SNORM)
+ FB_FORMAT_STR(GL_RGB10_A2)
+ FB_FORMAT_STR(GL_RGB10_A2UI)
+ FB_FORMAT_STR(GL_RGBA12)
+ FB_FORMAT_STR(GL_RGBA16)
+ FB_FORMAT_STR(GL_SRGB8)
+ FB_FORMAT_STR(GL_SRGB8_ALPHA8)
+ FB_FORMAT_STR(GL_R16F)
+ FB_FORMAT_STR(GL_RG16F)
+ FB_FORMAT_STR(GL_RGB16F)
+ FB_FORMAT_STR(GL_RGBA16F)
+ FB_FORMAT_STR(GL_R32F)
+ FB_FORMAT_STR(GL_RG32F)
+ FB_FORMAT_STR(GL_RGB32F)
+ FB_FORMAT_STR(GL_RGBA32F)
+ FB_FORMAT_STR(GL_R11F_G11F_B10F)
+ FB_FORMAT_STR(GL_RGB9_E5)
+ FB_FORMAT_STR(GL_R8I)
+ FB_FORMAT_STR(GL_R8UI)
+ FB_FORMAT_STR(GL_R16I)
+ FB_FORMAT_STR(GL_R16UI)
+ FB_FORMAT_STR(GL_R32I)
+ FB_FORMAT_STR(GL_R32UI)
+ FB_FORMAT_STR(GL_RG8I)
+ FB_FORMAT_STR(GL_RG8UI)
+ FB_FORMAT_STR(GL_RG16I)
+ FB_FORMAT_STR(GL_RG16UI)
+ FB_FORMAT_STR(GL_RG32I)
+ FB_FORMAT_STR(GL_RG32UI)
+ FB_FORMAT_STR(GL_RGB8I)
+ FB_FORMAT_STR(GL_RGB8UI)
+ FB_FORMAT_STR(GL_RGB16I)
+ FB_FORMAT_STR(GL_RGB16UI)
+ FB_FORMAT_STR(GL_RGB32I)
+ FB_FORMAT_STR(GL_RGB32UI)
+ FB_FORMAT_STR(GL_RGBA8I)
+ FB_FORMAT_STR(GL_RGBA8UI)
+ FB_FORMAT_STR(GL_RGBA16I)
+ FB_FORMAT_STR(GL_RGBA16UI)
+ FB_FORMAT_STR(GL_RGBA32I)
+ FB_FORMAT_STR(GL_RGBA32UI)
+ };
+
+ for( int i=0; i<vg_list_size(formats); i++ )
+ if( formats[i].e == format )
+ return formats[i].str;
+
+ return "UNDEFINED";