diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ab3c80d4..29403cf28 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -294,6 +294,8 @@ if(RENDERER_MODERN_OPENGL) ${MODERN_GLSL_DIR}/hud_draw_polygon.vertex.glsl ${MODERN_GLSL_DIR}/lighting.compute.glsl ${MODERN_GLSL_DIR}/lighting_copy.compute.glsl + ${MODERN_GLSL_DIR}/wboit_compose.fragment.glsl + ${MODERN_GLSL_DIR}/wboit_compose.vertex.glsl ${MODERN_GLSL_DIR}/post_process_screen.fragment.glsl ${MODERN_GLSL_DIR}/post_process_screen.vertex.glsl ${MODERN_GLSL_DIR}/simple.fragment.glsl diff --git a/src/cl_ents.c b/src/cl_ents.c index e22b89b29..6ed4bad87 100644 --- a/src/cl_ents.c +++ b/src/cl_ents.c @@ -181,7 +181,17 @@ void CL_AddEntityToList(visentlist_t* list, visentlist_entrytype_t vistype, enti ent = &list->list[cl_visents.count].ent; list->list[cl_visents.count].type = type; - list->list[cl_visents.count].distance = VectorDistanceQuick(cl.simorg, ent->origin); + if (vistype == visent_alpha) { + int i; + vec3_t displacement; + for (i = 0; i < 3; i++) { + displacement[i] = cl.simorg[i] - ent->origin[i]; + displacement[i] -= bound(ent->model->mins[i], displacement[i], ent->model->maxs[i]); + } + list->list[cl_visents.count].distance = DotProduct(displacement, displacement); + } else { + list->list[cl_visents.count].distance = VectorDistanceQuick(cl.simorg, ent->origin); + } list->list[cl_visents.count].draw[vistype] = true; ent->outlineScale = 0.5f * (r_refdef2.outlineBase + DotProduct(ent->origin, r_refdef2.outline_vpn)); @@ -233,6 +243,9 @@ void CL_AddEntity(entity_t *ent) vistype = visent_firstpass; ent->renderfx |= RF_NOSHADOW; } + else if (ent->alpha > 0.0f && ent->alpha < 1.0f) { + vistype = visent_alpha; + } else { vistype = visent_normal; } diff --git a/src/gl_framebuffer.c b/src/gl_framebuffer.c index 0eaf8740d..e53226802 100644 --- a/src/gl_framebuffer.c +++ b/src/gl_framebuffer.c @@ -100,6 +100,8 @@ static const char* framebuffer_texture_names[] = { "depth", // fbtex_depth "env", // fbtex_bloom, "norms", // fbtex_worldnormals, + "wboit-accum", // fbtex_oit_accumulate + "wboit-reveal", // fbtex_oit_reveal }; static qbool framebuffer_depth_buffer[] = { false, // framebuffer_none @@ -195,6 +197,7 @@ GL_StaticProcedureDeclaration(glBlitNamedFramebuffer, "readFrameBuffer=%u, drawF GL_StaticProcedureDeclaration(glDrawBuffers, "n=%d, bufs=%p", GLsizei n, const GLenum* bufs) GL_StaticProcedureDeclaration(glClearBufferfv, "buffer=%u, drawbuffer=%d, value=%p", GLenum buffer, GLint drawbuffer, const GLfloat* value) GL_StaticProcedureDeclaration(glClipControl, "origin=%u, depth=%u", GLenum origin, GLenum depth) +GL_StaticProcedureDeclaration(glBlendFunci, "attachment=%u, src=%u, dst=%u", GLenum attachment, GLenum src, GLenum dst) // Multi-sampled GL_StaticProcedureDeclaration(glRenderbufferStorageMultisample, "target=%x, samples=%d, internalformat=%x, width=%d, height=%d", GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height) @@ -285,6 +288,8 @@ void GL_InitialiseFramebufferHandling(void) GL_LoadOptionalFunction(glClipControl); } + GL_LoadOptionalFunction(glBlendFunci); + memset(framebuffer_data, 0, sizeof(framebuffer_data)); } @@ -585,6 +590,112 @@ qbool GL_FramebufferEndWorldNormals(framebuffer_id id) return true; } +qbool GL_FrameBufferStartOrderIndependentTransparency(framebuffer_id id) +{ + struct oit_s { + fbtex_id id; + GLenum fmt; + GLenum attachment; + }; + framebuffer_data_t* fb = NULL; + GLenum buffers[2] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }; + float accumClearValue[] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float revealClearValue[] = { 1.0f, 1.0f, 1.0f, 1.0f }; + int i; + + struct oit_s textures[] = { + { + .id = fbtex_oit_accumulate, + .fmt = GL_RGBA16F, + .attachment = GL_COLOR_ATTACHMENT0, + }, + { + .id = fbtex_oit_reveal, + .fmt = GL_R16F, + .attachment = GL_COLOR_ATTACHMENT1, + }, + }; + + if (!GL_Supported(R_SUPPORT_FRAMEBUFFERS)) { + return false; + } + + id = VID_MultisampledAlternateId(id); + fb = &framebuffer_data[id]; + if (!fb->glref) { + return false; + } + + for (i = 0; i < sizeof(textures) / sizeof(textures[0]); i++) { + struct oit_s tex = textures[i]; + + if (!R_TextureReferenceIsValid(fb->texture[tex.id])) { + char label[128]; + + strlcpy(label, framebuffer_names[id], sizeof(label)); + strlcat(label, "/", sizeof(label)); + strlcat(label, framebuffer_texture_names[tex.id], sizeof(label)); + + if (fb->samples) { + GL_CreateTexturesWithIdentifier(texture_type_2d_multisampled, 1, &fb->texture[tex.id], label); + if (!R_TextureReferenceIsValid(fb->texture[tex.id])) { + return false; + } + GL_TexStorage2DMultisample(fb->texture[tex.id], fb->samples, tex.fmt, fb->width, fb->height, EZ_USE_FIXED_SAMPLE_LOCATIONS); + } + else { + GL_CreateTexturesWithIdentifier(texture_type_2d, 1, &fb->texture[tex.id], label); + if (!R_TextureReferenceIsValid(fb->texture[tex.id])) { + return false; + } + GL_TexStorage2D(fb->texture[tex.id], 1, tex.fmt, fb->width, fb->height, false); + renderer.TextureSetFiltering(fb->texture[tex.id], texture_minification_nearest, texture_magnification_nearest); + renderer.TextureWrapModeClamp(fb->texture[tex.id]); + } + R_TextureSetFlag(fb->texture[tex.id], R_TextureGetFlag(fb->texture[tex.id]) | TEX_NO_TEXTUREMODE); + } + + GL_FramebufferTexture(fb->glref, tex.attachment, GL_TextureNameFromReference(fb->texture[tex.id]), 0); + } + GL_Procedure(glDrawBuffers, 2, buffers); + GL_Procedure(glClearBufferfv, GL_COLOR, 0, accumClearValue); + GL_Procedure(glClearBufferfv, GL_COLOR, 1, revealClearValue); + + return true; +} + +void GL_OitBlend(void) { + GL_Procedure(glBlendFunci, 0, GL_ONE, GL_ONE); + GL_Procedure(glBlendFunci, 1, GL_ZERO, GL_ONE_MINUS_SRC_COLOR); +} + +qbool GL_FrameBufferEndOrderIndependentTransparency(framebuffer_id id) +{ + framebuffer_data_t* fb = NULL; + GLenum buffer = GL_COLOR_ATTACHMENT0; + + if (!GL_Supported(R_SUPPORT_FRAMEBUFFERS)) { + return false; + } + + id = VID_MultisampledAlternateId(id); + fb = &framebuffer_data[id]; + if (!fb->glref) { + return false; + } + + GL_FramebufferTexture(fb->glref, GL_COLOR_ATTACHMENT0, GL_TextureNameFromReference(fb->texture[fbtex_standard]), 0); + GL_FramebufferTexture(fb->glref, GL_COLOR_ATTACHMENT1, 0, 0); + GL_Procedure(glDrawBuffers, 1, &buffer); + + if (fb->samples && id == framebuffer_std_ms) { + // Resolve multi-samples + GL_MultiSamplingResolve(framebuffer_std_ms, framebuffer_std, fbtex_oit_reveal, framebuffer_std_blit_ms, framebuffer_std_blit); + } + return true; +} + + void GL_FramebufferDelete(framebuffer_id id) { int i; diff --git a/src/gl_framebuffer.h b/src/gl_framebuffer.h index 87d822b35..1d2eb0963 100644 --- a/src/gl_framebuffer.h +++ b/src/gl_framebuffer.h @@ -39,6 +39,8 @@ typedef enum { fbtex_depth, fbtex_bloom, fbtex_worldnormals, + fbtex_oit_accumulate, + fbtex_oit_reveal, fbtex_count } fbtex_id; @@ -61,6 +63,9 @@ qbool GL_FramebufferEnabled3D(void); qbool GL_FramebufferStartWorldNormals(framebuffer_id id); qbool GL_FramebufferEndWorldNormals(framebuffer_id id); +qbool GL_FrameBufferStartOrderIndependentTransparency(framebuffer_id id); +qbool GL_FrameBufferEndOrderIndependentTransparency(framebuffer_id id); + int GL_FramebufferMultisamples(framebuffer_id framebuffer); void GL_FramebufferDeleteAll(void); int GL_FramebufferFxaaPreset(void); diff --git a/src/gl_program.c b/src/gl_program.c index 9a37b705b..38efab095 100644 --- a/src/gl_program.c +++ b/src/gl_program.c @@ -1306,11 +1306,17 @@ static void GL_BuildCoreDefinitions(void) "uniform vec3 fogColor;\n" "#endif\n" "vec4 applyFog(vec4 vecinput, float z) {\n" + "#ifdef EZQ_REVERSED_DEPTH\n" + "// z = 1.0 - z;\n" + "#endif\n" " float fogmix = exp(-fogDensity * z);\n" " fogmix = clamp(fogmix, 0.0, 1.0); \n" " return vec4(mix(fogColor, vecinput.rgb, fogmix), 1) * vecinput.a; \n" "}\n" "vec4 applyFogBlend(vec4 vecinput, float z) {\n" + "#ifdef EZQ_REVERSED_DEPTH\n" + " z = 1.0 - z;\n" + "#endif\n" " float fogmix = exp(-fogDensity * z);\n" " fogmix = clamp(fogmix, 0.0, 1.0); \n" " return vecinput * vec4(1, 1, 1, fogmix);\n" @@ -1323,11 +1329,17 @@ static void GL_BuildCoreDefinitions(void) "#endif" "\n" "vec4 applyFog(vec4 vecinput, float z) {\n" + "#ifdef EZQ_REVERSED_DEPTH\n" + " z = 1.0 - z;\n" + "#endif\n" " float fogmix = exp2(-fogDensity * z * z * LOG2);\n" " fogmix = clamp(fogmix, 0.0, 1.0);\n" " return vec4(mix(fogColor, vecinput.rgb, fogmix), 1) * vecinput.a;\n" "}\n" "vec4 applyFogBlend(vec4 vecinput, float z) {\n" + "#ifdef EZQ_REVERSED_DEPTH\n" + " z = 1.0 - z;\n" + "#endif\n" " float fogmix = exp2(-fogDensity * z * z * LOG2);\n" " fogmix = clamp(fogmix, 0.0, 1.0);\n" " return vecinput * vec4(1, 1, 1, fogmix);\n" @@ -1340,11 +1352,17 @@ static void GL_BuildCoreDefinitions(void) "#endif" "\n" "vec4 applyFog(vec4 vecinput, float z) {\n" + "#ifdef EZQ_REVERSED_DEPTH\n" + "z = 1.0 - z;\n" + "#endif\n" "float fogmix = (fogMaxZ - z) / (fogMaxZ - fogMinZ); \n" "fogmix = clamp(fogmix, 0.0, 1.0); \n" "return vec4(mix(fogColor, vecinput.rgb / vecinput.a, fogmix), 1) * vecinput.a; \n" "}\n" "vec4 applyFogBlend(vec4 vecinput, float z) {\n" + "#ifdef EZQ_REVERSED_DEPTH\n" + " z = 1.0 - z;\n" + "#endif\n" "float fogmix = (fogMaxZ - z) / (fogMaxZ - fogMinZ); \n" "fogmix = clamp(fogmix, 0.0, 1.0); \n" "return vecinput * vec4(1, 1, 1, fogmix);\n" @@ -1364,6 +1382,7 @@ static void GL_BuildCoreDefinitions(void) GL_DefineProgram_VF(r_program_aliasmodel, "aliasmodel", true, draw_aliasmodel, renderer_modern, GLM_CompileAliasModelProgram, STDOPTIONS_FOG); GL_DefineProgram_VF(r_program_brushmodel, "brushmodel", true, draw_world, renderer_modern, GLM_CompileDrawWorldProgram, STDOPTIONS_FOG); GL_DefineProgram_VF(r_program_brushmodel_alphatested, "brushmodel-alphatested", true, draw_world, renderer_modern, GLM_CompileDrawWorldProgramAlphaTested, STDOPTIONS_FOG); + GL_DefineProgram_VF(r_program_brushmodel_translucent, "brushmodel-translucent", true, draw_world, renderer_modern, GLM_CompileDrawWorldProgramTranslucent, STDOPTIONS_FOG); GL_DefineProgram_VF(r_program_sprite3d, "3d-sprites", false, draw_sprites, renderer_modern, GLM_Compile3DSpriteProgram, STDOPTIONS_FOG); GL_DefineProgram_VF(r_program_hud_images, "image-draw", true, hud_draw_image, renderer_modern, GLM_CreateMultiImageProgram, STDOPTIONS_NONE); GL_DefineProgram_VF(r_program_hud_circles, "circle-draw", false, hud_draw_circle, renderer_modern, GLM_CompileHudCircleProgram, STDOPTIONS_NONE); @@ -1372,6 +1391,7 @@ static void GL_BuildCoreDefinitions(void) GL_DefineProgram_VF(r_program_fx_world_geometry, "world-geometry", true, fx_world_geometry, renderer_modern, GLM_CompileWorldGeometryProgram, STDOPTIONS_NONE); GL_DefineProgram_VF(r_program_simple, "simple", false, simple, renderer_modern, GLM_CompileSimpleProgram, STDOPTIONS_NONE); GL_DefineProgram_VF(r_program_simple3d, "simple3d", false, simple3d, renderer_modern, GLM_CompileSimple3dProgram, STDOPTIONS_NONE); + GL_DefineProgram_VF(r_program_wboit_compose, "wboit-compose", true, wboit_compose, renderer_modern, GLM_CompileWorldGeometryProgram, STDOPTIONS_NONE); #endif #ifdef RENDERER_OPTION_CLASSIC_OPENGL diff --git a/src/gl_state.c b/src/gl_state.c index 5ad7a85f6..489676975 100644 --- a/src/gl_state.c +++ b/src/gl_state.c @@ -153,6 +153,8 @@ static GLenum glBlendFuncValuesSource[] = { GL_ONE, // r_blendfunc_src_one_dest_zero, GL_ZERO, // r_blendfunc_src_zero_dest_one, GL_ONE, // r_blendfunc_src_one_dest_one_minus_src_color, + GL_ONE, // r_blendfunc_src_one_dest_one + GL_SRC_ALPHA, // r_blendfunc_src_alpha_dest_one_minus_src_alpha }; static GLenum glBlendFuncValuesDestination[] = { GL_ZERO, // r_blendfunc_overwrite, @@ -166,6 +168,8 @@ static GLenum glBlendFuncValuesDestination[] = { GL_ZERO, // r_blendfunc_src_one_dest_zero, GL_ONE, // r_blendfunc_src_zero_dest_one, GL_ONE_MINUS_SRC_COLOR, // r_blendfunc_src_one_dest_one_minus_src_color, + GL_ONE, // r_blendfunc_src_one_dest_one + GL_ONE_MINUS_SRC_ALPHA, // r_blendfunc_src_alpha_dest_one_minus_src_alpha }; static GLenum glPolygonModeValues[] = { GL_FILL, // r_polygonmode_fill, @@ -217,6 +221,8 @@ static const char* txtBlendFuncNames[] = { "src_one_dest_zero", // r_blendfunc_src_one_dest_zero "src_zero_dest_one", // r_blendfunc_src_zero_dest_one "src_one_dest_one_minus_src_color", // "r_blendfunc_src_one_dest_one_minus_src_color" + "src_one_dest_one", // "r_blendfunc_src_one_dest_one" + "src_alpha_dest_one_minus_src_alpha", // "r_blendfunc_src_alpha_dest_one_minus_src_alpha" }; static const char* txtPolygonModeValues[] = { "fill", // r_polygonmode_fill, @@ -359,6 +365,9 @@ rendering_state_t* R_Init3DSpriteRenderingState(r_state_id id, const char* name) current->field = state->field; \ } +// TODO +void GL_OitBlend(void); + void GL_ApplyRenderingState(r_state_id id) { rendering_state_t* state = &states[id]; @@ -397,6 +406,10 @@ void GL_ApplyRenderingState(r_state_id id) ); R_TraceLogAPICall("glBlendFunc(%s)", txtBlendFuncNames[state->blendFunc]); } + if (state->blendFunc == r_blendfunc_src_one_dest_one) + { + GL_OitBlend(); + } if (state->line.width != current->line.width) { glLineWidth(current->line.width = state->line.width); R_TraceLogAPICall("glLineWidth(%f)", current->line.width); diff --git a/src/gl_texture_functions.c b/src/gl_texture_functions.c index 219ec1a3e..3bf2ed1f0 100644 --- a/src/gl_texture_functions.c +++ b/src/gl_texture_functions.c @@ -271,8 +271,31 @@ void GL_TexStorage2D( int level; GLsizei level_width = width; GLsizei level_height = height; - GLenum format = (internalformat == GL_RGBA8 || internalformat == GL_SRGB8_ALPHA8 || internalformat == GL_RGBA16F ? GL_RGBA : GL_RGB); - GLenum type = (internalformat == GL_RGBA16F || internalformat == GL_RGB16F ? GL_FLOAT : GL_UNSIGNED_BYTE); + GLenum format; + GLenum type; + + switch (internalformat) { + case GL_RGBA8: + case GL_RGB16F: + case GL_SRGB8_ALPHA8: + format = GL_RGBA; + break; + case GL_R32F: + format = GL_RED; + default: + format = GL_RGB; + break; + } + switch (internalformat) { + case GL_RGBA16F: + case GL_RGB16F: + case GL_R32F: + type = GL_FLOAT; + break; + default: + type = GL_UNSIGNED_BYTE; + break; + } // this might be completely useless (we don't upload data anyway) but just to keep all calls to the texture consistent format = ((is_lightmap && GL_Supported(R_SUPPORT_BGRA_LIGHTMAPS)) ? GL_BGRA : format); diff --git a/src/glm_aliasmodel.c b/src/glm_aliasmodel.c index fb6b5cab7..4659cd2c9 100644 --- a/src/glm_aliasmodel.c +++ b/src/glm_aliasmodel.c @@ -48,6 +48,7 @@ typedef enum aliasmodel_draw_type_s { aliasmodel_draw_outlines_spec, aliasmodel_draw_shells, aliasmodel_draw_postscene, + aliasmodel_draw_postscene_viewmodel, aliasmodel_draw_postscene_additive, aliasmodel_draw_postscene_shells, @@ -304,7 +305,7 @@ static void GLM_QueueAliasModelDrawImpl( outline = false; } else if ((render_effects & RF_WEAPONMODEL) && color[3] < 1) { - type = aliasmodel_draw_postscene; + type = aliasmodel_draw_postscene_viewmodel; shelltype = aliasmodel_draw_postscene_shells; outline = false; } @@ -502,7 +503,7 @@ static void GLM_RenderPreparedEntities(aliasmodel_draw_type_t type) renderer.TextureUnitBind(TEXTURE_UNIT_MATERIAL, shelltexture); } - if (translucent && !shells) { + if (type == aliasmodel_draw_postscene_viewmodel) { GLM_StateBeginAliasModelZPassBatch(); for (i = 0; i < instr->num_calls; ++i) { GL_MultiDrawArraysIndirect( @@ -577,6 +578,7 @@ void GLM_DrawAliasModelBatches(void) void GLM_DrawAliasModelPostSceneBatches(void) { GLM_RenderPreparedEntities(aliasmodel_draw_postscene); + GLM_RenderPreparedEntities(aliasmodel_draw_postscene_viewmodel); GLM_RenderPreparedEntities(aliasmodel_draw_postscene_additive); GLM_RenderPreparedEntities(aliasmodel_draw_postscene_shells); } diff --git a/src/glm_local.h b/src/glm_local.h index fe6350963..c23a46bc0 100644 --- a/src/glm_local.h +++ b/src/glm_local.h @@ -64,8 +64,6 @@ typedef struct uniform_block_frame_constants_s { // [4-byte break] int r_width; int r_height; - float r_inv_width; - float r_inv_height; float r_zFar; float r_zNear; @@ -79,6 +77,8 @@ typedef struct uniform_block_frame_constants_s { // camangles [0] float camangles[3]; // [1] [2] + float r_inv_width; + float r_inv_height; } uniform_block_frame_constants_t; #define MAX_WORLDMODEL_BATCH 64 @@ -129,7 +129,8 @@ void GLM_StateBeginImageDraw(void); typedef enum { opaque_world, // also contains alpha-tested - alpha_surfaces + alpha_surfaces, + translucent_surfaces, } glm_brushmodel_drawcall_type; void GLM_DrawWorldModelBatch(glm_brushmodel_drawcall_type type); diff --git a/src/glm_rmain.c b/src/glm_rmain.c index 21ae8e0cb..ac09d90c2 100644 --- a/src/glm_rmain.c +++ b/src/glm_rmain.c @@ -55,6 +55,21 @@ qbool GLM_CompileWorldGeometryProgram(void) return R_ProgramReady(r_program_fx_world_geometry) && GLM_CompilePostProcessVAO(); } +// If this returns false then the framebuffer will be blitted instead +qbool GLM_CompileWboitComposeProgram(void) +{ + int post_process_flags = 0; + + if (R_ProgramRecompileNeeded(r_program_wboit_compose, post_process_flags)) { + // Initialise program for drawing image + R_ProgramCompile(r_program_wboit_compose); + + R_ProgramSetCustomOptions(r_program_wboit_compose, post_process_flags); + } + + return R_ProgramReady(r_program_wboit_compose) && GLM_CompilePostProcessVAO(); +} + static void GLM_DrawWorldOutlines(void) { texture_ref normals = GL_FramebufferTextureReference(framebuffer_std, fbtex_worldnormals); @@ -99,6 +114,44 @@ static void GLM_DrawWorldOutlines(void) } } +static void GLM_DrawOrderIndependentTransparency(void) +{ + texture_ref accum = GL_FramebufferTextureReference(framebuffer_std, fbtex_oit_accumulate); + texture_ref reveal = GL_FramebufferTextureReference(framebuffer_std, fbtex_oit_reveal); + + if (R_TextureReferenceIsValid(accum) && R_TextureReferenceIsValid(reveal) && GLM_CompileWboitComposeProgram()) { + int viewport[4]; + int fullscreen_viewport[4]; + + R_GetViewport(viewport); + + // If we are only rendering to a section of the screen then that is the only part of the texture that will be filled in + if (CL_MultiviewEnabled()) { + R_GetFullScreenViewport(fullscreen_viewport); + R_Viewport(fullscreen_viewport[0], fullscreen_viewport[1], fullscreen_viewport[2], fullscreen_viewport[3]); + R_EnableScissorTest(viewport[0], viewport[1], viewport[2], viewport[3]); + } else { + // ignore viewsize and allat crap and set the viewport size to the whole window. + // previously the viewport was already resized, and then resized again later, making the outlines not align. + R_Viewport(0, 0, VID_ScaledWidth3D(), VID_ScaledHeight3D()); + } + + renderer.TextureUnitBind(0, accum); + renderer.TextureUnitBind(1, reveal); + + R_ProgramUse(r_program_wboit_compose); + R_ApplyRenderingState(r_state_oit_compose); + + GL_DrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + // Restore viewport + R_Viewport(viewport[0], viewport[1], viewport[2], viewport[3]); + if (CL_MultiviewEnabled()) { + R_DisableScissorTest(); + } + } +} + void GLM_RenderView(void) { GLM_UploadFrameConstants(); @@ -124,7 +177,16 @@ void GLM_RenderView(void) GLM_DrawWorldModelBatch(alpha_surfaces); + /* + GL_FrameBufferStartOrderIndependentTransparency(framebuffer_std); + GLM_DrawWorldModelBatch(translucent_surfaces); + GL_FrameBufferEndOrderIndependentTransparency(framebuffer_std); + */ + + GLM_DrawOrderIndependentTransparency(); + GLM_DrawAliasModelPostSceneBatches(); + } void GLM_PrepareModelRendering(qbool vid_restart) diff --git a/src/glm_rsurf.c b/src/glm_rsurf.c index 37cb69f4a..b43cc08a6 100644 --- a/src/glm_rsurf.c +++ b/src/glm_rsurf.c @@ -113,6 +113,10 @@ static void GLM_CheckDrawCallSize(void) #define DRAW_DRAWFLAT_BRIGHT (1 << 12) #define DRAW_ALPHATESTED (1 << 13) #define DRAW_SKYWIND (1 << 14) +#define DRAW_REVERSED_DEPTH (1 << 15) +#define DRAW_OIT (1 << 16) + +#define ENT_ALPHA(x) (x->alpha <= 0.0f ? 1.0f : x->alpha) static int material_samplers_max; static int TEXTURE_UNIT_MATERIAL; // Must always be the first non-standard texture unit @@ -124,7 +128,7 @@ static int TEXTURE_UNIT_SKYDOME_TEXTURE; static int TEXTURE_UNIT_SKYDOME_CLOUD_TEXTURE; // We re-compile whenever certain options change, to save texture bindings/lookups -static qbool GLM_CompileDrawWorldProgramImpl(r_program_id program_id, qbool alpha_test) +static qbool GLM_CompileDrawWorldProgramImpl(r_program_id program_id, qbool alpha_test, qbool oit) { extern cvar_t gl_lumatextures; extern cvar_t gl_textureless; @@ -151,7 +155,9 @@ static qbool GLM_CompileDrawWorldProgramImpl(r_program_id program_id, qbool alph (gl_textureless.integer ? DRAW_TEXTURELESS : 0) | ((gl_outline.integer & 2) ? DRAW_GEOMETRY : 0) | (alpha_test ? DRAW_ALPHATESTED : 0) | - (skywind ? DRAW_SKYWIND : 0); + (skywind ? DRAW_SKYWIND : 0) | + (glConfig.reversed_depth ? DRAW_REVERSED_DEPTH : 0) | + (oit ? DRAW_OIT : 0); if (R_ProgramRecompileNeeded(program_id, drawworld_desiredOptions)) { static char included_definitions[2048]; @@ -190,6 +196,12 @@ static qbool GLM_CompileDrawWorldProgramImpl(r_program_id program_id, qbool alph if (drawworld_desiredOptions & DRAW_ALPHATESTED) { strlcat(included_definitions, "#define DRAW_ALPHATEST_ENABLED\n", sizeof(included_definitions)); } + if (drawworld_desiredOptions & DRAW_REVERSED_DEPTH) { + strlcat(included_definitions, "#define EZQ_REVERSED_DEPTH\n", sizeof(included_definitions)); + } + if (drawworld_desiredOptions & DRAW_OIT) { + strlcat(included_definitions, "#define DRAW_OIT\n", sizeof(included_definitions)); + } if (skybox) { TEXTURE_UNIT_SKYBOX = samplers++; @@ -251,12 +263,17 @@ static qbool GLM_CompileDrawWorldProgramImpl(r_program_id program_id, qbool alph qbool GLM_CompileDrawWorldProgram(void) { - return GLM_CompileDrawWorldProgramImpl(r_program_brushmodel, false); + return GLM_CompileDrawWorldProgramImpl(r_program_brushmodel, false, false); } qbool GLM_CompileDrawWorldProgramAlphaTested(void) { - return GLM_CompileDrawWorldProgramImpl(r_program_brushmodel_alphatested, true); + return GLM_CompileDrawWorldProgramImpl(r_program_brushmodel_alphatested, true, false); +} + +qbool GLM_CompileDrawWorldProgramTranslucent(void) +{ + return GLM_CompileDrawWorldProgramImpl(r_program_brushmodel_translucent, false, true); } static glm_worldmodel_req_t* GLM_CurrentRequest(void) @@ -301,6 +318,7 @@ void GLM_EnterBatchedWorldRegion(void) { GLM_CompileDrawWorldProgram(); GLM_CompileDrawWorldProgramAlphaTested(); + GLM_CompileDrawWorldProgramTranslucent(); current_drawcall = 0; index_count = 0; @@ -340,7 +358,7 @@ static qbool GLM_AssignTexture(int texture_num, texture_t* texture) drawcall->mappings[index].arrayIndex = texture->gl_texture_index; drawcall->mappings[index].flags = R_TextureReferenceIsValid(texture->fb_texturenum) && texture->isLumaTexture ? EZQ_SURFACE_HAS_LUMA : 0; drawcall->mappings[index].flags |= R_TextureReferenceIsValid(texture->fb_texturenum) && !texture->isLumaTexture ? EZQ_SURFACE_HAS_FB : 0; - drawcall->mappings[index].flags |= R_TextureReferenceIsValid(texture->fb_texturenum) && texture->isAlphaTested ? EZQ_SURFACE_ALPHATEST : 0; + drawcall->mappings[index].flags |= R_TextureReferenceIsValid(texture->fb_texturenum) && texture->isAlphaTested ? 0 : 0; // EZQ_SURFACE_ALPHATEST : 0; drawcall->mappings[index].flags |= texture->isLitTurb ? EZQ_SURFACE_LIT_TURB : 0; return true; } @@ -389,6 +407,8 @@ static glm_worldmodel_req_t* GLM_NextBatchRequest(model_t* model, float alpha, i glm_brushmodel_drawcall_t* drawcall = &drawcalls[current_drawcall]; float mvMatrix[16]; + glm_brushmodel_drawcall_type desired_type = alpha < 1.0f ? alpha_surfaces : opaque_world; + R_GetModelviewMatrix(mvMatrix); // If user has switched off caustics (or no texture), ignore @@ -397,7 +417,12 @@ static glm_worldmodel_req_t* GLM_NextBatchRequest(model_t* model, float alpha, i } // See if previous batch has same texture & matrix, if so just continue - if (drawcall->batch_count) { + if (drawcall->type != desired_type) + { + drawcall = GL_FlushWorldModelBatch(); + drawcall->type = desired_type; + } + else if (drawcall->batch_count) { req = &drawcall->worldmodel_requests[drawcall->batch_count - 1]; if (allow_duplicate && model == req->model && req->samplerMappingCount == num_textures && req->firstTexture == first_texture && drawcall->batch_count < MAX_WORLDMODEL_BATCH && isAlphaTested == req->isAlphaTested) { @@ -417,7 +442,7 @@ static glm_worldmodel_req_t* GLM_NextBatchRequest(model_t* model, float alpha, i } // Try and continue the previous batch - if (worldmodel == req->worldmodel && !memcmp(req->mvMatrix, mvMatrix, sizeof(req->mvMatrix)) && polygonOffset == req->polygonOffset && req->flags == flags && req->isAlphaTested == isAlphaTested) { + if (worldmodel == req->worldmodel && !memcmp(req->mvMatrix, mvMatrix, sizeof(req->mvMatrix)) && polygonOffset == req->polygonOffset && req->flags == flags && req->isAlphaTested == isAlphaTested && req->alpha == alpha) { if (num_textures == 0) { // We don't care about materials, so can draw with previous batch return req; @@ -508,6 +533,8 @@ void GLM_DrawWaterSurfaces(void) } } + GL_FlushWorldModelBatch(); + R_TraceLeaveNamedRegion(); waterchain = NULL; @@ -588,13 +615,17 @@ qbool GLM_CompileSimple3dProgram(void) static glm_brushmodel_drawcall_t* GL_FlushWorldModelBatch(void) { - const glm_brushmodel_drawcall_t* prev; + glm_brushmodel_drawcall_t* prev = &drawcalls[current_drawcall]; glm_brushmodel_drawcall_t* current; - int last = current_drawcall++; + + if (prev->batch_count == 0) { + return prev; + } + + current_drawcall++; GLM_CheckDrawCallSize(); - prev = &drawcalls[last]; current = &drawcalls[current_drawcall]; memset(current, 0, sizeof(*current)); @@ -658,23 +689,27 @@ void GLM_PrepareWorldModelBatch(void) static void GLM_DrawWorldExecuteCalls(glm_brushmodel_drawcall_t* drawcall, uintptr_t offset, int begin, int count) { int i; + int prevSampler = -1; qbool prev_alphaTested = false; for (i = begin; i < begin + count; ++i) { glm_worldmodel_req_t* req = &drawcall->worldmodel_requests[i]; int batchCount = 1; + int sampler = req->nonDynamicSampler; if (req->isAlphaTested != prev_alphaTested) { if (req->isAlphaTested) { R_ProgramUse(r_program_brushmodel_alphatested); + R_ProgramUniform1i(r_program_uniform_brushmodel_alphatested_sampler, prevSampler = sampler); } else { R_ProgramUse(r_program_brushmodel); + R_ProgramUniform1i(r_program_uniform_brushmodel_sampler, prevSampler = sampler); } prev_alphaTested = req->isAlphaTested; } - while (i + batchCount < begin + count && drawcall->worldmodel_requests[i + batchCount].isAlphaTested == req->isAlphaTested) { + while (i + batchCount < begin + count && drawcall->worldmodel_requests[i + batchCount].nonDynamicSampler == sampler && drawcall->worldmodel_requests[i + batchCount].isAlphaTested == req->isAlphaTested) { ++batchCount; } @@ -760,13 +795,15 @@ void GLM_DrawBrushModel(entity_t* ent, qbool polygonOffset, qbool caustics) int i; glm_worldmodel_req_t* req = NULL; model_t* model = ent->model; + qbool had_flat = false; - if (GLM_DuplicatePreviousRequest(model, 1.0f, model->last_texture_chained - model->first_texture_chained + 1, model->first_texture_chained, polygonOffset, caustics)) { + if (GLM_DuplicatePreviousRequest(model, ENT_ALPHA(ent), model->last_texture_chained - model->first_texture_chained + 1, model->first_texture_chained, polygonOffset, caustics)) { return; } if (model->drawflat_chain) { - req = GLM_NextBatchRequest(model, 1.0f, 0, 0, false, false, false, false); + had_flat = true; + req = GLM_NextBatchRequest(model, ENT_ALPHA(ent), 0, 0, false, false, false, false); req = GLM_DrawFlatChain(req, model->drawflat_chain); @@ -784,10 +821,10 @@ void GLM_DrawBrushModel(entity_t* ent, qbool polygonOffset, qbool caustics) continue; } - req = GLM_NextBatchRequest(model, 1.0f, 1, i, polygonOffset, caustics, false, tex->isAlphaTested); + req = GLM_NextBatchRequest(model, ENT_ALPHA(ent), 1, i, polygonOffset, caustics, false, tex->isAlphaTested); tex = R_TextureAnimation(ent, tex); if (!GLM_AssignTexture(i, tex)) { - req = GLM_NextBatchRequest(model, 1.0f, 1, i, polygonOffset, caustics, false, tex->isAlphaTested); + req = GLM_NextBatchRequest(model, ENT_ALPHA(ent), 1, i, polygonOffset, caustics, false, tex->isAlphaTested); GLM_AssignTexture(i, tex); } @@ -838,7 +875,11 @@ static void GL_SortDrawCalls(glm_brushmodel_drawcall_t* drawcall) } } - qsort(drawcall->worldmodel_requests, drawcall->batch_count, sizeof(drawcall->worldmodel_requests[0]), GL_DrawCallComparison); + // Translucent bmodels are put into requests based on their distance from view + // and sorting here will break that order. + if (drawcall->type == opaque_world) { + qsort(drawcall->worldmodel_requests, drawcall->batch_count, sizeof(drawcall->worldmodel_requests[0]), GL_DrawCallComparison); + } for (i = 0; i < drawcall->batch_count; ++i) { glm_worldmodel_req_t* thisReq = &drawcall->worldmodel_requests[i]; diff --git a/src/glsl/common.glsl b/src/glsl/common.glsl index f0a2a5f07..f35d7f956 100644 --- a/src/glsl/common.glsl +++ b/src/glsl/common.glsl @@ -60,8 +60,6 @@ layout(std140, binding=EZQ_GL_BINDINGPOINT_FRAMECONSTANTS) uniform GlobalState { // [4-byte break] int r_width; int r_height; - float r_inv_width; - float r_inv_height; float r_zFar; float r_zNear; @@ -75,6 +73,8 @@ layout(std140, binding=EZQ_GL_BINDINGPOINT_FRAMECONSTANTS) uniform GlobalState { // camAngles.x vec3 camAngles; // camAngles.yz + float r_inv_width; + float r_inv_height; }; struct WorldDrawInfo { diff --git a/src/glsl/draw_world.fragment.glsl b/src/glsl/draw_world.fragment.glsl index 9c19666b0..1e0b1aac1 100644 --- a/src/glsl/draw_world.fragment.glsl +++ b/src/glsl/draw_world.fragment.glsl @@ -45,10 +45,13 @@ in vec4 UnClipped; in flat int sampler_index; in float mix_floor; in float mix_wall; +in float alpha; layout(location=0) out vec4 frag_colour; #ifdef DRAW_GEOMETRY layout(location=1) out vec4 normal_texture; +#elif defined(DRAW_OIT) +layout(location=1) out float reveal; #endif // Drawflat mode TINTED... Amend texture color based on floor/wall @@ -141,6 +144,8 @@ void main() } // Avoid black artifacts at border between texture and transparency visible in fog texColor = vec4(texColor.rgb, 1.0); +#else + texColor *= alpha; #endif turbType = Flags & EZQ_SURFACE_TYPE; @@ -200,12 +205,17 @@ void main() #endif } else { - frag_colour = texColor * waterAlpha; + frag_colour = texColor; if ((Flags & EZQ_SURFACE_LIT_TURB) > 0) { frag_colour = vec4(lmColor.rgb, 1) * frag_colour; } #ifdef DRAW_FOG - frag_colour = applyFog(frag_colour, gl_FragCoord.z / gl_FragCoord.w); +#ifdef EZQ_REVERSED_DEPTH + float d = 1 / gl_FragCoord.w; +#else + float d = gl_FragCoord.w; +#endif + frag_colour = applyFog(frag_colour, d / gl_FragCoord.w); #endif } } @@ -232,11 +242,11 @@ void main() texColor = vec4(mix(texColor.rgb, texColor.rgb + lumaColor.rgb, min(1, Flags & EZQ_SURFACE_HAS_LUMA)), texColor.a); #endif texColor = applyColorTinting(texColor); - frag_colour = vec4(lmColor.rgb, 1) * texColor; + frag_colour = vec4(lmColor.rgb * texColor.a, texColor.a) * texColor; #if defined(DRAW_LUMA_TEXTURES) && defined(DRAW_LUMA_TEXTURES_FB) lumaColor = applyColorTinting(lumaColor); frag_colour = vec4(mix(frag_colour.rgb, frag_colour.rgb + lumaColor.rgb, min(1, Flags & EZQ_SURFACE_HAS_LUMA)), frag_colour.a); - frag_colour = vec4(mix(frag_colour.rgb, lumaColor.rgb, min(1, Flags & EZQ_SURFACE_HAS_FB) * lumaColor.a), frag_colour.a); + frag_colour = vec4(mix(frag_colour.rgb, lumaColor.rgb * texColor.a, min(1, Flags & EZQ_SURFACE_HAS_FB) * lumaColor.a), frag_colour.a); #elif !defined(DRAW_LUMA_TEXTURES) && defined(DRAW_LUMA_TEXTURES_FB) // GL_DECAL lumaColor = applyColorTinting(lumaColor); @@ -252,7 +262,19 @@ void main() #endif #ifdef DRAW_FOG - frag_colour = applyFog(frag_colour, gl_FragCoord.z / gl_FragCoord.w); +#ifdef EZQ_REVERSED_DEPTH + float d = 1 / gl_FragCoord.w; +#else + float d = gl_FragCoord.w; +#endif + frag_colour = applyFog(frag_colour, d / gl_FragCoord.w); +#endif + +#ifdef DRAW_OIT + float z = 1./gl_FragCoord.w; + float weight = clamp(alpha * 0.03 / (1e-5 + pow(z/1e7, 1.0)), 1e-2, 3e3); + frag_colour = vec4(frag_colour.rgb * alpha, alpha) * weight; + reveal = alpha; #endif } } diff --git a/src/glsl/draw_world.vertex.glsl b/src/glsl/draw_world.vertex.glsl index 810379561..1876a0dad 100644 --- a/src/glsl/draw_world.vertex.glsl +++ b/src/glsl/draw_world.vertex.glsl @@ -37,6 +37,7 @@ layout(std140, binding = EZQ_GL_BINDINGPOINT_WORLDMODEL_SURFACES) buffer surface out flat int sampler_index; out float mix_floor; out float mix_wall; +out float alpha; layout(std140, binding=EZQ_GL_BINDINGPOINT_BRUSHMODEL_DRAWDATA) buffer WorldCvars { WorldDrawInfo drawInfo[]; @@ -53,6 +54,7 @@ void main() int drawCallFlags = drawInfo[_instanceId].drawFlags; int textureFlags = samplerMapping[drawInfo[_instanceId].samplerBase + materialNumber].flags; sampler_index = drawInfo[_instanceId].sampler; + alpha = drawInfo[_instanceId].alpha; gl_Position = projectionMatrix * drawInfo[_instanceId].mvMatrix * vec4(position, 1.0); #ifdef DRAW_GEOMETRY diff --git a/src/glsl/wboit_compose.fragment.glsl b/src/glsl/wboit_compose.fragment.glsl new file mode 100644 index 000000000..8f2df1fa7 --- /dev/null +++ b/src/glsl/wboit_compose.fragment.glsl @@ -0,0 +1,35 @@ +#version 430 + +layout (location = 0) out vec4 frag; + +layout (binding = 0) uniform sampler2D accum; +layout (binding = 1) uniform sampler2D reveal; + +bool isApproximatelyEqual(float a, float b) +{ + return abs(a - b) <= (abs(a) < abs(b) ? abs(b) : abs(a)) * 1e-5; +} + +float max3(vec3 v) +{ + return max(max(v.x, v.y), v.z); +} + +void main() +{ + ivec2 coords = ivec2(gl_FragCoord.xy); + + float revealage = texelFetch(reveal, coords, 0).r; + + if (isApproximatelyEqual(revealage, 1.0f)) + discard; + + vec4 accumulation = texelFetch(accum, coords, 0); + + if (isinf(max3(abs(accumulation.rgb)))) + accumulation.rgb = vec3(accumulation.a); + + vec3 average_color = accumulation.rgb / max(accumulation.a, 1e-5); + + frag = vec4(clamp(average_color, 0.0, 1.0), 1.0f - revealage); +} \ No newline at end of file diff --git a/src/glsl/wboit_compose.vertex.glsl b/src/glsl/wboit_compose.vertex.glsl new file mode 100644 index 000000000..713af9ff1 --- /dev/null +++ b/src/glsl/wboit_compose.vertex.glsl @@ -0,0 +1,8 @@ +#version 430 + +layout(location = 0) in vec2 inPosition; + +void main() +{ + gl_Position = vec4(inPosition, 0, 1); +} diff --git a/src/r_program.h b/src/r_program.h index a1175a83c..aa779b0a3 100644 --- a/src/r_program.h +++ b/src/r_program.h @@ -30,8 +30,10 @@ typedef enum { r_program_fx_world_geometry, r_program_brushmodel_alphatested, + r_program_brushmodel_translucent, r_program_simple, r_program_simple3d, + r_program_wboit_compose, r_program_count } r_program_id; diff --git a/src/r_rmain.c b/src/r_rmain.c index 71f6171bc..3725878ee 100644 --- a/src/r_rmain.c +++ b/src/r_rmain.c @@ -892,14 +892,19 @@ void R_RenderView(void) renderer.DrawWaterSurfaces(); } + // TODO: probably also alpha stuffs + if (R_UseModernOpenGL() || R_UseVulkan()) { + R_DrawViewModel(); + } + + // Draws transparent world surfaces + renderer.DrawWaterSurfaces(); + R_DrawEntities(); // Adds 3d effects (particles, lights, chat icons etc) R_Render3DEffects(); - // Draws transparent world surfaces - renderer.DrawWaterSurfaces(); - // Render billboards renderer.Draw3DSpritesInline(); @@ -1124,7 +1129,7 @@ void R_PolyBlend(void) renderer.PolyBlend(v_blend); } -static void R_DrawEntities(void) +static void R_DrawEntities(qbool translucent) { visentlist_entrytype_t ent_type; @@ -1145,8 +1150,5 @@ static void R_DrawEntities(void) for (ent_type = 0; ent_type < visent_max; ++ent_type) { R_DrawEntitiesOnList(&cl_visents, ent_type); } - if (R_UseModernOpenGL() || R_UseVulkan()) { - R_DrawViewModel(); - } R_TraceLeaveNamedRegion(); } diff --git a/src/r_state.h b/src/r_state.h index db62f4cb5..d950bef28 100644 --- a/src/r_state.h +++ b/src/r_state.h @@ -53,7 +53,8 @@ typedef enum { r_blendfunc_src_one_dest_zero, r_blendfunc_src_zero_dest_one, r_blendfunc_src_one_dest_one_minus_src_color, - + r_blendfunc_src_one_dest_one, + r_blendfunc_src_alpha_dest_one_minus_src_alpha, r_blendfunc_count } r_blendfunc_t; @@ -323,6 +324,9 @@ typedef enum { r_state_fx_world_geometry, + r_state_oit_accumulate, + r_state_oit_compose, + r_state_count } r_state_id; diff --git a/src/r_states.c b/src/r_states.c index 0bf48f0c1..a4ef2422f 100644 --- a/src/r_states.c +++ b/src/r_states.c @@ -115,6 +115,7 @@ static void R_InitialiseWorldStates(void) state->blendFunc = r_blendfunc_premultiplied_alpha; state = R_InitRenderingState(r_state_alpha_surfaces_offset_glm, true, "glmAlphaOffsetWorldState", vao_brushmodel); + state->depth.mask_enabled = false; state->polygonOffset.option = r_polygonoffset_standard; state->blendingEnabled = true; state->blendFunc = r_blendfunc_premultiplied_alpha; @@ -123,10 +124,15 @@ static void R_InitialiseWorldStates(void) state->polygonOffset.option = r_polygonoffset_standard; state = R_InitRenderingState(r_state_alpha_surfaces_glm, true, "glmAlphaWorldState", vao_brushmodel); + state->depth.mask_enabled = false; state->blendingEnabled = true; state->blendFunc = r_blendfunc_premultiplied_alpha; state->polygonOffset.option = r_polygonoffset_standard; + state = R_CopyRenderingState(r_state_oit_accumulate, r_state_alpha_surfaces_glm, "glmAlphaWorldStateOitAccumulate"); + state->blendFunc = r_blendfunc_src_one_dest_one; + state->polygonOffset.option = r_polygonoffset_standard; + R_InitRenderingState(r_state_opaque_surfaces_glm, true, "glmWorldState", vao_brushmodel); } @@ -228,6 +234,12 @@ static void R_Initialise2DStates(void) R_GLC_EnableAlphaTesting(state); // really? state->blendingEnabled = true; state->blendFunc = r_blendfunc_premultiplied_alpha; + + state = R_InitRenderingState(r_state_oit_compose, true, "glmOitAlphaCompose", postprocess_vao); + state->depth.test_enabled = false; + state->depth.mask_enabled = false; + state->blendingEnabled = true; + state->blendFunc = r_blendfunc_src_alpha_dest_one_minus_src_alpha; } static void R_InitialiseSpriteStates(void) @@ -384,6 +396,7 @@ static void R_InitialiseEntityStates(void) state = R_CopyRenderingState(r_state_aliasmodel_translucent_batch, r_state_aliasmodel_opaque_batch, "aliasModelTranslucentBatchState"); state->blendFunc = r_blendfunc_premultiplied_alpha; state->blendingEnabled = true; + state->depth.mask_enabled = false; state = R_CopyRenderingState(r_state_aliasmodel_translucent_batch_zpass, r_state_aliasmodel_opaque_batch, "aliasModelTranslucentBatchZPass"); state->colorMask[0] = state->colorMask[1] = state->colorMask[2] = state->colorMask[3] = false; @@ -391,6 +404,7 @@ static void R_InitialiseEntityStates(void) state = R_CopyRenderingState(r_state_aliasmodel_additive_batch, r_state_aliasmodel_opaque_batch, "aliasModelTranslucentBatchState"); state->blendFunc = r_blendfunc_additive_blending; state->blendingEnabled = true; + state->depth.mask_enabled = false; } static void R_InitialiseBrushModelStates(void)