319 lines
9.2 KiB
C++
319 lines
9.2 KiB
C++
/* -----------------------------------------------------------------------------
|
|
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 <RenderPrimitive *> &in, Stack <RenderPrimitive *> &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 <RenderPrimitive *> ∈
|
|
Stack <RenderPrimitive *> &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 <RenderPrimitive *> &_in, Stack <RenderPrimitive *> &_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 <RenderPrimitive *> &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 <Core::Light *> &light_list, const AutoStack <PrepareLightJob *> &setup_jobs, const Array <Stack <RenderPrimitive *> > &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 <RenderPrimitive *> &dl_list, DrawContext::Render rc)
|
|
{
|
|
ScopedPerfEvent event(this, __FUNCTION__, Color::Red);
|
|
|
|
if (dl_list.GetCount() == 0)
|
|
return;
|
|
|
|
// Setup all lights.
|
|
List <Core::Light *> frustum_light_list;
|
|
environment_interface->GetLightsInFrustum(view_item->GetMatrix().GetRow(3), frustum, frustum_light_list);
|
|
|
|
Array <Stack <RenderPrimitive *> > light_dl_list(frustum_light_list.GetCount());
|
|
AutoStack <PrepareLightJob *> 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);
|
|
}
|
|
//------------------------------------------------------------------------------
|