/* ----------------------------------------------------------------------------- GSFramework Copyright 2001-2013 Emmanuel Julien. All Rights Reserved. ----------------------------------------------------------------------------- */ #include "gpu/gpu_renderer.h" #include "gpu/gpu_shader_object.h" #include "gpu/gpu_geometry.h" #include "gpu/gpu_draw_context.h" #include "core/renderer_environment_interface.h" #include "core/geometry.h" #include "core/terrain.h" #include "core/camera.h" #include "core/light.h" #include "async/job.h" #include "alloc/ialloc.h" #include "platform.h" using namespace GS; using namespace GS::GPU; //------------------------------------------------------------------------------ void Renderer::LightCullDisplayList(const Core::Light &light, const Stack &in, Stack &out) { for (uint n = 0; n < in.GetCount(); ++n) { RenderPrimitive *dl = in[n]; switch (light.model) { case Core::Light::Model_Point: if (light.range > 0) { MinMax &mm = dl->dlst->minmax; Vector4 dl_lpos = light.GetMatrix().GetRow(3) * dl->item->GetInverseMatrix(); if ( ((dl_lpos.x - light.range) < mm.mx.x) && ((dl_lpos.x + light.range) > mm.mn.x) && ((dl_lpos.y - light.range) < mm.mx.y) && ((dl_lpos.y + light.range) > mm.mn.y) && ((dl_lpos.z - light.range) < mm.mx.z) && ((dl_lpos.z + light.range) > mm.mn.z) ) out.Push(dl); } else out.Push(dl); break; case Core::Light::Model_Spot: if (light.frustum.ClassifyMinMax(dl->dlst->minmax, &dl->item->GetMatrix()) != Frustum::Outside) out.Push(dl); break; case Core::Light::Model_Linear: default: out.Push(dl); break; } } } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void Renderer::SetupForwardContext(const DrawContext::Context &ctx) { switch (ctx.draw) { case DrawContext::Base: switch (ctx.render) { case DrawContext::Opaque: EnableDepthTest(true); break; case DrawContext::Alpha: EnableDepthWrite(false); EnableBlending(true); SetBlendFunc(BlendSrcAlpha, BlendOneMinusSrcAlpha); break; } break; case DrawContext::Light: switch (ctx.render) { case DrawContext::Opaque: EnableBlending(true); SetBlendFunc(BlendSrcAlpha, BlendOne); break; case DrawContext::Alpha: EnableDepthWrite(false); EnableBlending(true); SetBlendFunc(BlendSrcAlpha, BlendOne); break; } break; } } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ class GPU::PrepareLightJob : public ASync::Job { const Renderer &renderer; const Core::Light &light; const Stack ∈ Stack &out; DrawContext::Render rc; public: NPLACEMENT_NEW(RendererJob) bool use_shadow; bool CullPrimitive(const Render::Material *m, const MinMax &mm, const Core::Item *item) const { if (m->renderword & Core::Material::Render_Unlit) return false; // exclude from lighting switch (light.model) { case Core::Light::Model_Point: if (light.range > 0) { Vector4 dl_lpos = light.GetMatrix().GetRow(3) * item->GetInverseMatrix(); if ( ((dl_lpos.x - light.range) < mm.mx.x) && ((dl_lpos.y - light.range) < mm.mx.y) && ((dl_lpos.z - light.range) < mm.mx.z) && ((dl_lpos.x + light.range) > mm.mn.x) && ((dl_lpos.y + light.range) > mm.mn.y) && ((dl_lpos.z + light.range) > mm.mn.z) ) return true; } else return true; break; case Core::Light::Model_Spot: if (light.frustum.ClassifyMinMax(mm, &item->GetMatrix()) != Frustum::Outside) return true; break; default: case Core::Light::Model_Linear: return true; } return false; } void Execute(uint) { for (uint n = 0; n < in.GetCount(); ++n) { RenderPrimitive *dl = in[n]; bool r = false; switch (dl->type) { case RenderPrimitive::TypeDisplayList: r = CullPrimitive(dl->dlst->material, dl->dlst->minmax, dl->item); break; case RenderPrimitive::TypeTerrainPatch: r = CullPrimitive(dl->patch->terrain->render_data->material, dl->patch->minmax, dl->item); break; default: case RenderPrimitive::TypeEmitter: break; } if (r) out.Push(dl); } // Prepare shadow map if required. if (out.GetCount() == 0) use_shadow = false; else { use_shadow = renderer.gpu_config.enable_shadow && (light.shadow == Core::Light::Shadow_Map); if ((rc == DrawContext::Alpha) && (light.shadow_cast_all == false)) use_shadow = false; bool use_shadow_matrix = false; if ((light.model == Core::Light::Model_Spot) && !light.projection_texture.IsEmpty()) use_shadow_matrix = true; if (use_shadow || use_shadow_matrix) renderer.PrepareShadowMap(light, use_shadow); } } PrepareLightJob(const Renderer &_renderer, const Core::Light &_light, const Stack &_in, Stack &_out, DrawContext::Render _rc) : Job("Light Draw Setup"), renderer(_renderer), light(_light), in(_in), out(_out), rc(_rc) {} }; //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ static MaterialShader::Variant DispatchLightShaderVariant(Core::Light::Model type, bool shadow, bool projection_map) { static MaterialShader::Variant table[2][2][Core::Light::Model_Last] = { { { MaterialShader::Last, MaterialShader::FS_PointLight, MaterialShader::FS_LinearLight, MaterialShader::FS_SpotLight }, { MaterialShader::Last, MaterialShader::FS_PointLightShadowMapping, MaterialShader::FS_LinearLightShadowMapping, MaterialShader::FS_SpotLightShadowMapping } }, { { MaterialShader::Last, MaterialShader::FS_PointLight, MaterialShader::FS_LinearLight, MaterialShader::FS_SpotLightProjection }, { MaterialShader::Last, MaterialShader::FS_PointLightShadowMapping, MaterialShader::FS_LinearLightShadowMapping, MaterialShader::FS_SpotLightProjectionShadowMapping } } }; return table[projection_map ? 1 : 0][shadow ? 1 : 0][type]; } void Renderer::DrawForwardBaseConstant(DrawContext::Render rc, const Stack &dl_list) { ScopedPerfEvent event(this, __FUNCTION__, Color::Blue); DrawContext dc(rc, DrawContext::Base, MaterialShader::FS_Constant); SetupForwardContext(dc.ctx); DrawList(dl_list, dc); } void Renderer::DrawForwardLightContribution(DrawContext::Render rc, const List &light_list, const AutoStack &setup_jobs, const Array > &dl_list) { ScopedPerfEvent event(this, "Render light contribution", Color::Blue); DrawContext dc(rc, DrawContext::Light, MaterialShader::FS_Constant); SetupForwardContext(dc.ctx); EnableDepthWrite(false); SetDepthFunc(DepthLessEqual); uint job_count = 0; ListForeachPtr(Core::Light *, l, light_list) { if (l->model == Core::Light::Model_None) continue; // Wait for the light setup job to complete. Platform::Get().job_manager->JoinJob(setup_jobs[job_count]); if (dl_list[job_count].GetCount()) { bool use_shadow = setup_jobs[job_count]->use_shadow; if (use_shadow) { PerfBeginEvent("Render shadow map", Color::Yellow); EnableDepthWrite(true); RenderShadowMap(*l); EnableDepthWrite(false); SetCurrentFBO(gpu_config.use_rtt ? render_fbo : NULL); SetupForwardContext(dc.ctx); PerfEndEvent(); } // Add contribution. dc.ctx.variant = DispatchLightShaderVariant(l->model, use_shadow, asbool(l->projection_texture)); dc.light = l; DrawList(dl_list[job_count], dc); } ++stats.light_processed; ++job_count; } } void Renderer::RenderListForward(Stack &dl_list, DrawContext::Render rc) { ScopedPerfEvent event(this, __FUNCTION__, Color::Red); if (dl_list.GetCount() == 0) return; // Setup all lights. List frustum_light_list; environment_interface->GetLightsInFrustum(view_item->GetMatrix().GetRow(3), frustum, frustum_light_list); Array > light_dl_list(frustum_light_list.GetCount()); AutoStack light_setup_job(frustum_light_list.GetCount()); uint job_count = 0; ListForeachPtr(Core::Light *, l, frustum_light_list) { if (l->model == Core::Light::Model_None) continue; if (PrepareLightJob *job = new PrepareLightJob(*this, *l, dl_list, light_dl_list[job_count++], rc)) { light_setup_job.Push(job); Platform::Get().job_manager->EnqueueJob(job); } } // Draw all passes. DrawForwardBaseConstant(rc, dl_list); DrawForwardLightContribution(rc, frustum_light_list, light_setup_job, light_dl_list); // CollectJobsPerf(light_setup_job, job_count, stats.bench_prepare_light); // Restore default state. SetCullFunc(CullFront); SetDepthFunc(DepthLess); EnableDepthWrite(true); EnableBlending(false); SetBlendFunc(BlendSrcAlpha, BlendOneMinusSrcAlpha); } //------------------------------------------------------------------------------