mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-04 10:54:24 -04:00
3343 lines
113 KiB
C++
3343 lines
113 KiB
C++
/**
|
|
* PANDA 3D SOFTWARE
|
|
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
|
*
|
|
* All use of this software is subject to the terms of the revised BSD
|
|
* license. You should have received a copy of this license along
|
|
* with this source code in a file named "LICENSE."
|
|
*
|
|
* @file graphicsStateGuardian.cxx
|
|
* @author drose
|
|
* @date 1999-02-02
|
|
* @author fperazzi, PandaSE
|
|
* @date 2010-05-05
|
|
* _max_2d_texture_array_layers, _supports_2d_texture_array,
|
|
* get_supports_cg_profile)
|
|
*/
|
|
|
|
#include "graphicsStateGuardian.h"
|
|
#include "graphicsEngine.h"
|
|
#include "config_display.h"
|
|
#include "textureContext.h"
|
|
#include "vertexBufferContext.h"
|
|
#include "indexBufferContext.h"
|
|
#include "renderBuffer.h"
|
|
#include "light.h"
|
|
#include "planeNode.h"
|
|
#include "ambientLight.h"
|
|
#include "throw_event.h"
|
|
#include "clockObject.h"
|
|
#include "pStatTimer.h"
|
|
#include "pStatGPUTimer.h"
|
|
#include "geomTristrips.h"
|
|
#include "geomTrifans.h"
|
|
#include "geomLinestrips.h"
|
|
#include "colorWriteAttrib.h"
|
|
#include "shader.h"
|
|
#include "pnotify.h"
|
|
#include "drawableRegion.h"
|
|
#include "displayRegion.h"
|
|
#include "graphicsOutput.h"
|
|
#include "texturePool.h"
|
|
#include "geomMunger.h"
|
|
#include "stateMunger.h"
|
|
#include "ambientLight.h"
|
|
#include "directionalLight.h"
|
|
#include "pointLight.h"
|
|
#include "spotlight.h"
|
|
#include "textureReloadRequest.h"
|
|
#include "shaderAttrib.h"
|
|
#include "materialAttrib.h"
|
|
#include "depthWriteAttrib.h"
|
|
#include "lightAttrib.h"
|
|
#include "texGenAttrib.h"
|
|
#include "shaderGenerator.h"
|
|
#include "lightLensNode.h"
|
|
#include "colorAttrib.h"
|
|
#include "colorScaleAttrib.h"
|
|
#include "clipPlaneAttrib.h"
|
|
#include "fogAttrib.h"
|
|
#include "config_pstats.h"
|
|
|
|
#include <algorithm>
|
|
#include <limits.h>
|
|
|
|
PStatCollector GraphicsStateGuardian::_vertex_buffer_switch_pcollector("Buffer switch:Vertex");
|
|
PStatCollector GraphicsStateGuardian::_index_buffer_switch_pcollector("Buffer switch:Index");
|
|
PStatCollector GraphicsStateGuardian::_shader_buffer_switch_pcollector("Buffer switch:Shader");
|
|
PStatCollector GraphicsStateGuardian::_load_vertex_buffer_pcollector("Draw:Transfer data:Vertex buffer");
|
|
PStatCollector GraphicsStateGuardian::_load_index_buffer_pcollector("Draw:Transfer data:Index buffer");
|
|
PStatCollector GraphicsStateGuardian::_load_shader_buffer_pcollector("Draw:Transfer data:Shader buffer");
|
|
PStatCollector GraphicsStateGuardian::_create_vertex_buffer_pcollector("Draw:Transfer data:Create Vertex buffer");
|
|
PStatCollector GraphicsStateGuardian::_create_index_buffer_pcollector("Draw:Transfer data:Create Index buffer");
|
|
PStatCollector GraphicsStateGuardian::_create_shader_buffer_pcollector("Draw:Transfer data:Create Shader buffer");
|
|
PStatCollector GraphicsStateGuardian::_load_texture_pcollector("Draw:Transfer data:Texture");
|
|
PStatCollector GraphicsStateGuardian::_data_transferred_pcollector("Data transferred");
|
|
PStatCollector GraphicsStateGuardian::_texmgrmem_total_pcollector("Texture manager");
|
|
PStatCollector GraphicsStateGuardian::_texmgrmem_resident_pcollector("Texture manager:Resident");
|
|
PStatCollector GraphicsStateGuardian::_primitive_batches_pcollector("Primitive batches");
|
|
PStatCollector GraphicsStateGuardian::_primitive_batches_tristrip_pcollector("Primitive batches:Triangle strips");
|
|
PStatCollector GraphicsStateGuardian::_primitive_batches_trifan_pcollector("Primitive batches:Triangle fans");
|
|
PStatCollector GraphicsStateGuardian::_primitive_batches_tri_pcollector("Primitive batches:Triangles");
|
|
PStatCollector GraphicsStateGuardian::_primitive_batches_patch_pcollector("Primitive batches:Patches");
|
|
PStatCollector GraphicsStateGuardian::_primitive_batches_other_pcollector("Primitive batches:Other");
|
|
PStatCollector GraphicsStateGuardian::_vertices_tristrip_pcollector("Vertices:Triangle strips");
|
|
PStatCollector GraphicsStateGuardian::_vertices_trifan_pcollector("Vertices:Triangle fans");
|
|
PStatCollector GraphicsStateGuardian::_vertices_tri_pcollector("Vertices:Triangles");
|
|
PStatCollector GraphicsStateGuardian::_vertices_patch_pcollector("Vertices:Patches");
|
|
PStatCollector GraphicsStateGuardian::_vertices_other_pcollector("Vertices:Other");
|
|
PStatCollector GraphicsStateGuardian::_state_pcollector("State changes");
|
|
PStatCollector GraphicsStateGuardian::_transform_state_pcollector("State changes:Transforms");
|
|
PStatCollector GraphicsStateGuardian::_texture_state_pcollector("State changes:Textures");
|
|
PStatCollector GraphicsStateGuardian::_draw_primitive_pcollector("Draw:Primitive:Draw");
|
|
PStatCollector GraphicsStateGuardian::_draw_set_state_pcollector("Draw:Set State");
|
|
PStatCollector GraphicsStateGuardian::_clear_pcollector("Draw:Clear");
|
|
PStatCollector GraphicsStateGuardian::_flush_pcollector("Draw:Flush");
|
|
PStatCollector GraphicsStateGuardian::_compute_dispatch_pcollector("Draw:Compute dispatch");
|
|
|
|
PStatCollector GraphicsStateGuardian::_wait_occlusion_pcollector("Wait:Occlusion");
|
|
PStatCollector GraphicsStateGuardian::_wait_timer_pcollector("Wait:Timer Queries");
|
|
PStatCollector GraphicsStateGuardian::_timer_queries_pcollector("Timer queries");
|
|
PStatCollector GraphicsStateGuardian::_command_latency_pcollector("Command latency");
|
|
|
|
PStatCollector GraphicsStateGuardian::_prepare_pcollector("Draw:Prepare");
|
|
PStatCollector GraphicsStateGuardian::_prepare_texture_pcollector("Draw:Prepare:Texture");
|
|
PStatCollector GraphicsStateGuardian::_prepare_sampler_pcollector("Draw:Prepare:Sampler");
|
|
PStatCollector GraphicsStateGuardian::_prepare_geom_pcollector("Draw:Prepare:Geom");
|
|
PStatCollector GraphicsStateGuardian::_prepare_shader_pcollector("Draw:Prepare:Shader");
|
|
PStatCollector GraphicsStateGuardian::_prepare_vertex_buffer_pcollector("Draw:Prepare:Vertex buffer");
|
|
PStatCollector GraphicsStateGuardian::_prepare_index_buffer_pcollector("Draw:Prepare:Index buffer");
|
|
PStatCollector GraphicsStateGuardian::_prepare_shader_buffer_pcollector("Draw:Prepare:Shader buffer");
|
|
|
|
PStatCollector GraphicsStateGuardian::_draw_set_state_transform_pcollector("Draw:Set State:Transform");
|
|
PStatCollector GraphicsStateGuardian::_draw_set_state_alpha_test_pcollector("Draw:Set State:Alpha test");
|
|
PStatCollector GraphicsStateGuardian::_draw_set_state_antialias_pcollector("Draw:Set State:Antialias");
|
|
PStatCollector GraphicsStateGuardian::_draw_set_state_clip_plane_pcollector("Draw:Set State:Clip plane");
|
|
PStatCollector GraphicsStateGuardian::_draw_set_state_color_pcollector("Draw:Set State:Color");
|
|
PStatCollector GraphicsStateGuardian::_draw_set_state_cull_face_pcollector("Draw:Set State:Cull face");
|
|
PStatCollector GraphicsStateGuardian::_draw_set_state_depth_offset_pcollector("Draw:Set State:Depth offset");
|
|
PStatCollector GraphicsStateGuardian::_draw_set_state_depth_test_pcollector("Draw:Set State:Depth test");
|
|
PStatCollector GraphicsStateGuardian::_draw_set_state_depth_write_pcollector("Draw:Set State:Depth write");
|
|
PStatCollector GraphicsStateGuardian::_draw_set_state_render_mode_pcollector("Draw:Set State:Render mode");
|
|
PStatCollector GraphicsStateGuardian::_draw_set_state_rescale_normal_pcollector("Draw:Set State:Rescale normal");
|
|
PStatCollector GraphicsStateGuardian::_draw_set_state_shade_model_pcollector("Draw:Set State:Shade model");
|
|
PStatCollector GraphicsStateGuardian::_draw_set_state_blending_pcollector("Draw:Set State:Blending");
|
|
PStatCollector GraphicsStateGuardian::_draw_set_state_shader_pcollector("Draw:Set State:Shader");
|
|
PStatCollector GraphicsStateGuardian::_draw_set_state_shader_parameters_pcollector("Draw:Set State:Shader Parameters");
|
|
PStatCollector GraphicsStateGuardian::_draw_set_state_texture_pcollector("Draw:Set State:Texture");
|
|
PStatCollector GraphicsStateGuardian::_draw_set_state_tex_matrix_pcollector("Draw:Set State:Tex matrix");
|
|
PStatCollector GraphicsStateGuardian::_draw_set_state_tex_gen_pcollector("Draw:Set State:Tex gen");
|
|
PStatCollector GraphicsStateGuardian::_draw_set_state_material_pcollector("Draw:Set State:Material");
|
|
PStatCollector GraphicsStateGuardian::_draw_set_state_light_pcollector("Draw:Set State:Light");
|
|
PStatCollector GraphicsStateGuardian::_draw_set_state_stencil_pcollector("Draw:Set State:Stencil");
|
|
PStatCollector GraphicsStateGuardian::_draw_set_state_fog_pcollector("Draw:Set State:Fog");
|
|
PStatCollector GraphicsStateGuardian::_draw_set_state_scissor_pcollector("Draw:Set State:Scissor");
|
|
|
|
PT(TextureStage) GraphicsStateGuardian::_alpha_scale_texture_stage = NULL;
|
|
|
|
TypeHandle GraphicsStateGuardian::_type_handle;
|
|
|
|
/**
|
|
*
|
|
*/
|
|
|
|
GraphicsStateGuardian::
|
|
GraphicsStateGuardian(CoordinateSystem internal_coordinate_system,
|
|
GraphicsEngine *engine, GraphicsPipe *pipe) :
|
|
_internal_coordinate_system(internal_coordinate_system),
|
|
_pipe(pipe),
|
|
_engine(engine)
|
|
{
|
|
_coordinate_system = CS_invalid;
|
|
_internal_transform = TransformState::make_identity();
|
|
|
|
if (_internal_coordinate_system == CS_default) {
|
|
_internal_coordinate_system = get_default_coordinate_system();
|
|
}
|
|
|
|
set_coordinate_system(get_default_coordinate_system());
|
|
|
|
_data_reader = (GeomVertexDataPipelineReader *)NULL;
|
|
_current_display_region = (DisplayRegion*)NULL;
|
|
_current_stereo_channel = Lens::SC_mono;
|
|
_current_tex_view_offset = 0;
|
|
_current_lens = (Lens *)NULL;
|
|
_projection_mat = TransformState::make_identity();
|
|
_projection_mat_inv = TransformState::make_identity();
|
|
|
|
_needs_reset = true;
|
|
_is_valid = false;
|
|
_current_properties = NULL;
|
|
_closing_gsg = false;
|
|
_active = true;
|
|
_prepared_objects = new PreparedGraphicsObjects;
|
|
_stereo_buffer_mask = ~0;
|
|
_incomplete_render = allow_incomplete_render;
|
|
_effective_incomplete_render = false;
|
|
_loader = Loader::get_global_ptr();
|
|
|
|
_is_hardware = false;
|
|
_prefers_triangle_strips = false;
|
|
_max_vertices_per_array = INT_MAX;
|
|
_max_vertices_per_primitive = INT_MAX;
|
|
|
|
// Initially, we set this to 1 (the default--no multitexturing supported).
|
|
// A derived GSG may set this differently if it supports multitexturing.
|
|
_max_texture_stages = 1;
|
|
|
|
// Also initially, we assume there are no limits on texture sizes, and that
|
|
// 3-d and cube-map textures are not supported.
|
|
_max_texture_dimension = -1;
|
|
_max_3d_texture_dimension = 0;
|
|
_max_2d_texture_array_layers = 0;
|
|
_max_cube_map_dimension = 0;
|
|
_max_buffer_texture_size = 0;
|
|
|
|
// Assume we don't support these fairly advanced texture combiner modes.
|
|
_supports_texture_combine = false;
|
|
_supports_texture_saved_result = false;
|
|
_supports_texture_dot3 = false;
|
|
|
|
_supports_3d_texture = false;
|
|
_supports_2d_texture_array = false;
|
|
_supports_cube_map = false;
|
|
_supports_buffer_texture = false;
|
|
_supports_cube_map_array = false;
|
|
_supports_tex_non_pow2 = false;
|
|
_supports_texture_srgb = false;
|
|
_supports_compressed_texture = false;
|
|
_compressed_texture_formats.clear();
|
|
_compressed_texture_formats.set_bit(Texture::CM_off);
|
|
|
|
// Assume no limits on number of lights or clip planes.
|
|
_max_lights = -1;
|
|
_max_clip_planes = -1;
|
|
|
|
// Assume no vertex blending capability.
|
|
_max_vertex_transforms = 0;
|
|
_max_vertex_transform_indices = 0;
|
|
|
|
_supports_occlusion_query = false;
|
|
_supports_timer_query = false;
|
|
|
|
#ifdef DO_PSTATS
|
|
_timer_queries_active = false;
|
|
_last_query_frame = 0;
|
|
_last_num_queried = 0;
|
|
// _timer_delta = 0.0;
|
|
|
|
_pstats_gpu_thread = -1;
|
|
#endif
|
|
|
|
// Initially, we set this to false; a GSG that knows it has this property
|
|
// should set it to true.
|
|
_copy_texture_inverted = false;
|
|
|
|
// Similarly with these capabilities flags.
|
|
_supports_multisample = false;
|
|
_supports_generate_mipmap = false;
|
|
_supports_depth_texture = false;
|
|
_supports_depth_stencil = false;
|
|
_supports_shadow_filter = false;
|
|
_supports_sampler_objects = false;
|
|
_supports_basic_shaders = false;
|
|
_supports_geometry_shaders = false;
|
|
_supports_tessellation_shaders = false;
|
|
_supports_compute_shaders = false;
|
|
_supports_glsl = false;
|
|
_supports_hlsl = false;
|
|
|
|
_supports_stencil = false;
|
|
_supports_stencil_wrap = false;
|
|
_supports_two_sided_stencil = false;
|
|
_supports_geometry_instancing = false;
|
|
_supports_indirect_draw = false;
|
|
|
|
// Assume a maximum of 1 render target in absence of MRT.
|
|
_max_color_targets = 1;
|
|
_supports_dual_source_blending = false;
|
|
|
|
_supported_geom_rendering = 0;
|
|
|
|
// If this is true, then we can apply a color andor color scale by twiddling
|
|
// the material andor ambient light (which could mean enabling lighting even
|
|
// without a LightAttrib).
|
|
_color_scale_via_lighting = color_scale_via_lighting;
|
|
|
|
// Similarly for applying a texture to achieve uniform alpha scaling.
|
|
_alpha_scale_via_texture = alpha_scale_via_texture;
|
|
|
|
// Few GSG's can do this, since it requires touching each vertex as it is
|
|
// rendered.
|
|
_runtime_color_scale = false;
|
|
|
|
// The default is no shader support.
|
|
_auto_detect_shader_model = SM_00;
|
|
_shader_model = SM_00;
|
|
|
|
_gamma = 1.0f;
|
|
_texture_quality_override = Texture::QL_default;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
GraphicsStateGuardian::
|
|
~GraphicsStateGuardian() {
|
|
remove_gsg(this);
|
|
GeomMunger::unregister_mungers_for_gsg(this);
|
|
}
|
|
|
|
/**
|
|
* Returns the graphics engine that created this GSG. Since there is normally
|
|
* only one GraphicsEngine object in an application, this is usually the same
|
|
* as the global GraphicsEngine.
|
|
*/
|
|
GraphicsEngine *GraphicsStateGuardian::
|
|
get_engine() const {
|
|
nassertr(_engine != (GraphicsEngine *)NULL, GraphicsEngine::get_global_ptr());
|
|
return _engine;
|
|
}
|
|
|
|
/**
|
|
* Returns true if this particular GSG supports using the multisample bits to
|
|
* provide antialiasing, and also supports M_multisample and
|
|
* M_multisample_mask transparency modes. If this is not true for a
|
|
* particular GSG, Panda will map the M_multisample modes to M_binary.
|
|
*
|
|
* This method is declared virtual solely so that it can be queried from
|
|
* cullResult.cxx.
|
|
*/
|
|
bool GraphicsStateGuardian::
|
|
get_supports_multisample() const {
|
|
return _supports_multisample;
|
|
}
|
|
|
|
/**
|
|
* Returns the union of Geom::GeomRendering values that this particular GSG
|
|
* can support directly. If a Geom needs to be rendered that requires some
|
|
* additional properties, the StandardMunger and/or the CullableObject will
|
|
* convert it as needed.
|
|
*
|
|
* This method is declared virtual solely so that it can be queried from
|
|
* cullableObject.cxx.
|
|
*/
|
|
int GraphicsStateGuardian::
|
|
get_supported_geom_rendering() const {
|
|
return _supported_geom_rendering;
|
|
}
|
|
|
|
/**
|
|
* Returns true if this particular GSG supports the specified Cg Shader
|
|
* Profile.
|
|
*/
|
|
bool GraphicsStateGuardian::
|
|
get_supports_cg_profile(const string &name) const {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Changes the coordinate system in effect on this particular gsg. This is
|
|
* also called the "external" coordinate system, since it is the coordinate
|
|
* system used by the scene graph, external to to GSG.
|
|
*
|
|
* Normally, this will be the default coordinate system, but it might be set
|
|
* differently at runtime. It will automatically be copied from the current
|
|
* lens's coordinate system as each DisplayRegion is rendered.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
set_coordinate_system(CoordinateSystem cs) {
|
|
if (cs == CS_default) {
|
|
cs = get_default_coordinate_system();
|
|
}
|
|
if (_coordinate_system == cs) {
|
|
return;
|
|
}
|
|
_coordinate_system = cs;
|
|
|
|
// Changing the external coordinate system changes the cs_transform.
|
|
if (_internal_coordinate_system == CS_default ||
|
|
_internal_coordinate_system == _coordinate_system) {
|
|
_cs_transform = TransformState::make_identity();
|
|
_inv_cs_transform = TransformState::make_identity();
|
|
|
|
} else {
|
|
_cs_transform =
|
|
TransformState::make_mat
|
|
(LMatrix4::convert_mat(_coordinate_system,
|
|
_internal_coordinate_system));
|
|
_inv_cs_transform =
|
|
TransformState::make_mat
|
|
(LMatrix4::convert_mat(_internal_coordinate_system,
|
|
_coordinate_system));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the coordinate system used internally by the GSG. This may be the
|
|
* same as the external coordinate system reported by get_coordinate_system(),
|
|
* or it may be something different.
|
|
*
|
|
* In any case, vertices that have been transformed before being handed to the
|
|
* GSG (that is, vertices with a contents value of C_clip_point) will be
|
|
* expected to be in this coordinate system.
|
|
*/
|
|
CoordinateSystem GraphicsStateGuardian::
|
|
get_internal_coordinate_system() const {
|
|
return _internal_coordinate_system;
|
|
}
|
|
|
|
/**
|
|
* Returns the set of texture and geom objects that have been prepared with
|
|
* this GSG (and possibly other GSG's that share objects).
|
|
*/
|
|
PreparedGraphicsObjects *GraphicsStateGuardian::
|
|
get_prepared_objects() {
|
|
return _prepared_objects;
|
|
}
|
|
|
|
/**
|
|
* Set gamma. Returns true on success.
|
|
*/
|
|
bool GraphicsStateGuardian::
|
|
set_gamma(PN_stdfloat gamma) {
|
|
_gamma = gamma;
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get the current gamma setting.
|
|
*/
|
|
PN_stdfloat GraphicsStateGuardian::
|
|
get_gamma() const {
|
|
return _gamma;
|
|
}
|
|
|
|
/**
|
|
* Restore original gamma setting.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
restore_gamma() {
|
|
}
|
|
|
|
/**
|
|
* Calls the indicated function on all currently-prepared textures, or until
|
|
* the callback function returns false.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
traverse_prepared_textures(GraphicsStateGuardian::TextureCallback *func,
|
|
void *callback_arg) {
|
|
ReMutexHolder holder(_prepared_objects->_lock);
|
|
PreparedGraphicsObjects::Textures::const_iterator ti;
|
|
for (ti = _prepared_objects->_prepared_textures.begin();
|
|
ti != _prepared_objects->_prepared_textures.end();
|
|
++ti) {
|
|
bool result = (*func)(*ti, callback_arg);
|
|
if (!result) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
/**
|
|
* Sets the "flash texture". This is a debug feature; when enabled, the
|
|
* specified texture will begin flashing in the scene, helping you to find it
|
|
* visually.
|
|
*
|
|
* The texture also flashes with a color code: blue for mipmap level 0, yellow
|
|
* for mipmap level 1, and red for mipmap level 2 or higher (even for textures
|
|
* that don't have mipmaps). This gives you an idea of the choice of the
|
|
* texture size. If it is blue, the texture is being drawn the proper size or
|
|
* magnified; if it is yellow, it is being minified a little bit; and if it
|
|
* red, it is being minified considerably. If you see a red texture when you
|
|
* are right in front of it, you should consider reducing the size of the
|
|
* texture to avoid wasting texture memory.
|
|
*
|
|
* Not all rendering backends support the flash_texture feature. Presently,
|
|
* it is only supported by OpenGL.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
set_flash_texture(Texture *tex) {
|
|
_flash_texture = tex;
|
|
}
|
|
#endif // NDEBUG
|
|
|
|
#ifndef NDEBUG
|
|
/**
|
|
* Resets the "flash texture", so that no textures will flash. See
|
|
* set_flash_texture().
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
clear_flash_texture() {
|
|
_flash_texture = NULL;
|
|
}
|
|
#endif // NDEBUG
|
|
|
|
#ifndef NDEBUG
|
|
/**
|
|
* Returns the current "flash texture", if any, or NULL if none. See
|
|
* set_flash_texture().
|
|
*/
|
|
Texture *GraphicsStateGuardian::
|
|
get_flash_texture() const {
|
|
return _flash_texture;
|
|
}
|
|
#endif // NDEBUG
|
|
|
|
/**
|
|
* Sets the SceneSetup object that indicates the initial camera position, etc.
|
|
* This must be called before traversal begins. Returns true if the scene is
|
|
* acceptable, false if something's wrong. This should be called in the draw
|
|
* thread only.
|
|
*/
|
|
bool GraphicsStateGuardian::
|
|
set_scene(SceneSetup *scene_setup) {
|
|
_scene_setup = scene_setup;
|
|
_current_lens = scene_setup->get_lens();
|
|
if (_current_lens == (Lens *)NULL) {
|
|
return false;
|
|
}
|
|
|
|
set_coordinate_system(_current_lens->get_coordinate_system());
|
|
|
|
_projection_mat = calc_projection_mat(_current_lens);
|
|
if (_projection_mat == 0) {
|
|
return false;
|
|
}
|
|
_projection_mat_inv = _projection_mat->get_inverse();
|
|
return prepare_lens();
|
|
}
|
|
|
|
/**
|
|
* Returns the current SceneSetup object.
|
|
*/
|
|
SceneSetup *GraphicsStateGuardian::
|
|
get_scene() const {
|
|
return _scene_setup;
|
|
}
|
|
|
|
/**
|
|
* Creates whatever structures the GSG requires to represent the texture
|
|
* internally, and returns a newly-allocated TextureContext object with this
|
|
* data. It is the responsibility of the calling function to later call
|
|
* release_texture() with this same pointer (which will also delete the
|
|
* pointer).
|
|
*
|
|
* This function should not be called directly to prepare a texture. Instead,
|
|
* call Texture::prepare().
|
|
*/
|
|
TextureContext *GraphicsStateGuardian::
|
|
prepare_texture(Texture *) {
|
|
return (TextureContext *)NULL;
|
|
}
|
|
|
|
/**
|
|
* Ensures that the current Texture data is refreshed onto the GSG. This
|
|
* means updating the texture properties and/or re-uploading the texture
|
|
* image, if necessary. This should only be called within the draw thread.
|
|
*
|
|
* If force is true, this function will not return until the texture has been
|
|
* fully uploaded. If force is false, the function may choose to upload a
|
|
* simple version of the texture instead, if the texture is not fully resident
|
|
* (and if get_incomplete_render() is true).
|
|
*/
|
|
bool GraphicsStateGuardian::
|
|
update_texture(TextureContext *, bool) {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Frees the resources previously allocated via a call to prepare_texture(),
|
|
* including deleting the TextureContext itself, if it is non-NULL.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
release_texture(TextureContext *) {
|
|
}
|
|
|
|
/**
|
|
* This method should only be called by the GraphicsEngine. Do not call it
|
|
* directly; call GraphicsEngine::extract_texture_data() instead.
|
|
*
|
|
* This method will be called in the draw thread to download the texture
|
|
* memory's image into its ram_image value. It returns true on success, false
|
|
* otherwise.
|
|
*/
|
|
bool GraphicsStateGuardian::
|
|
extract_texture_data(Texture *) {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Creates whatever structures the GSG requires to represent the sampler
|
|
* internally, and returns a newly-allocated SamplerContext object with this
|
|
* data. It is the responsibility of the calling function to later call
|
|
* release_sampler() with this same pointer (which will also delete the
|
|
* pointer).
|
|
*
|
|
* This function should not be called directly to prepare a sampler. Instead,
|
|
* call Texture::prepare().
|
|
*/
|
|
SamplerContext *GraphicsStateGuardian::
|
|
prepare_sampler(const SamplerState &sampler) {
|
|
return (SamplerContext *)NULL;
|
|
}
|
|
|
|
/**
|
|
* Frees the resources previously allocated via a call to prepare_sampler(),
|
|
* including deleting the SamplerContext itself, if it is non-NULL.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
release_sampler(SamplerContext *) {
|
|
}
|
|
|
|
/**
|
|
* Prepares the indicated Geom for retained-mode rendering, by creating
|
|
* whatever structures are necessary in the GSG (for instance, vertex
|
|
* buffers). Returns the newly-allocated GeomContext that can be used to
|
|
* render the geom.
|
|
*/
|
|
GeomContext *GraphicsStateGuardian::
|
|
prepare_geom(Geom *) {
|
|
return (GeomContext *)NULL;
|
|
}
|
|
|
|
/**
|
|
* Frees the resources previously allocated via a call to prepare_geom(),
|
|
* including deleting the GeomContext itself, if it is non-NULL.
|
|
*
|
|
* This function should not be called directly to prepare a Geom. Instead,
|
|
* call Geom::prepare().
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
release_geom(GeomContext *) {
|
|
}
|
|
|
|
/**
|
|
* Compile a vertex/fragment shader body.
|
|
*/
|
|
ShaderContext *GraphicsStateGuardian::
|
|
prepare_shader(Shader *shader) {
|
|
return (ShaderContext *)NULL;
|
|
}
|
|
|
|
/**
|
|
* Releases the resources allocated by prepare_shader
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
release_shader(ShaderContext *sc) {
|
|
}
|
|
|
|
/**
|
|
* Prepares the indicated buffer for retained-mode rendering.
|
|
*/
|
|
VertexBufferContext *GraphicsStateGuardian::
|
|
prepare_vertex_buffer(GeomVertexArrayData *) {
|
|
return (VertexBufferContext *)NULL;
|
|
}
|
|
|
|
/**
|
|
* Frees the resources previously allocated via a call to prepare_data(),
|
|
* including deleting the VertexBufferContext itself, if necessary.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
release_vertex_buffer(VertexBufferContext *) {
|
|
}
|
|
|
|
/**
|
|
* Prepares the indicated buffer for retained-mode rendering.
|
|
*/
|
|
IndexBufferContext *GraphicsStateGuardian::
|
|
prepare_index_buffer(GeomPrimitive *) {
|
|
return (IndexBufferContext *)NULL;
|
|
}
|
|
|
|
/**
|
|
* Frees the resources previously allocated via a call to prepare_data(),
|
|
* including deleting the IndexBufferContext itself, if necessary.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
release_index_buffer(IndexBufferContext *) {
|
|
}
|
|
|
|
/**
|
|
* Prepares the indicated buffer for retained-mode rendering.
|
|
*/
|
|
BufferContext *GraphicsStateGuardian::
|
|
prepare_shader_buffer(ShaderBuffer *) {
|
|
return (BufferContext *)NULL;
|
|
}
|
|
|
|
/**
|
|
* Frees the resources previously allocated via a call to prepare_data(),
|
|
* including deleting the BufferContext itself, if necessary.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
release_shader_buffer(BufferContext *) {
|
|
}
|
|
|
|
/**
|
|
* Begins a new occlusion query. After this call, you may call
|
|
* begin_draw_primitives() and draw_triangles()/draw_whatever() repeatedly.
|
|
* Eventually, you should call end_occlusion_query() before the end of the
|
|
* frame; that will return a new OcclusionQueryContext object that will tell
|
|
* you how many pixels represented by the bracketed geometry passed the depth
|
|
* test.
|
|
*
|
|
* It is not valid to call begin_occlusion_query() between another
|
|
* begin_occlusion_query() .. end_occlusion_query() sequence.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
begin_occlusion_query() {
|
|
nassertv(_current_occlusion_query == (OcclusionQueryContext *)NULL);
|
|
}
|
|
|
|
/**
|
|
* Ends a previous call to begin_occlusion_query(). This call returns the
|
|
* OcclusionQueryContext object that will (eventually) report the number of
|
|
* pixels that passed the depth test between the call to
|
|
* begin_occlusion_query() and end_occlusion_query().
|
|
*/
|
|
PT(OcclusionQueryContext) GraphicsStateGuardian::
|
|
end_occlusion_query() {
|
|
nassertr(_current_occlusion_query != (OcclusionQueryContext *)NULL, NULL);
|
|
PT(OcclusionQueryContext) result = _current_occlusion_query;
|
|
_current_occlusion_query = NULL;
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Adds a timer query to the command stream, associated with the given PStats
|
|
* collector index.
|
|
*/
|
|
PT(TimerQueryContext) GraphicsStateGuardian::
|
|
issue_timer_query(int pstats_index) {
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Dispatches a currently bound compute shader using the given work group
|
|
* counts.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
dispatch_compute(int num_groups_x, int num_groups_y, int num_groups_z) {
|
|
nassertv(false /* Compute shaders not supported by GSG */);
|
|
}
|
|
|
|
/**
|
|
* Looks up or creates a GeomMunger object to munge vertices appropriate to
|
|
* this GSG for the indicated state.
|
|
*/
|
|
PT(GeomMunger) GraphicsStateGuardian::
|
|
get_geom_munger(const RenderState *state, Thread *current_thread) {
|
|
RenderState::Mungers &mungers = state->_mungers;
|
|
|
|
if (!mungers.is_empty()) {
|
|
// Before we even look up the map, see if the _last_mi value points to
|
|
// this GSG. This is likely because we tend to visit the same state
|
|
// multiple times during a frame. Also, this might well be the only GSG
|
|
// in the world anyway.
|
|
int mi = state->_last_mi;
|
|
if (mi >= 0 && mungers.has_element(mi) && mungers.get_key(mi) == this) {
|
|
PT(GeomMunger) munger = mungers.get_data(mi);
|
|
if (munger->is_registered()) {
|
|
return munger;
|
|
}
|
|
}
|
|
|
|
// Nope, we have to look it up in the map.
|
|
mi = mungers.find(this);
|
|
if (mi >= 0) {
|
|
PT(GeomMunger) munger = mungers.get_data(mi);
|
|
if (munger->is_registered()) {
|
|
state->_last_mi = mi;
|
|
return munger;
|
|
} else {
|
|
// This GeomMunger is no longer registered. Remove it from the map.
|
|
mungers.remove_element(mi);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Nothing in the map; create a new entry.
|
|
PT(GeomMunger) munger = make_geom_munger(state, current_thread);
|
|
nassertr(munger != (GeomMunger *)NULL && munger->is_registered(), munger);
|
|
nassertr(munger->is_of_type(StateMunger::get_class_type()), munger);
|
|
|
|
state->_last_mi = mungers.store(this, munger);
|
|
return munger;
|
|
}
|
|
|
|
/**
|
|
* Creates a new GeomMunger object to munge vertices appropriate to this GSG
|
|
* for the indicated state.
|
|
*/
|
|
PT(GeomMunger) GraphicsStateGuardian::
|
|
make_geom_munger(const RenderState *state, Thread *current_thread) {
|
|
// The default implementation returns no munger at all, but presumably,
|
|
// every kind of GSG needs some special munging action, so real GSG's will
|
|
// override this to return something more useful.
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* This function will compute the distance to the indicated point, assumed to
|
|
* be in eye coordinates, from the camera plane. The point is assumed to be
|
|
* in the GSG's internal coordinate system.
|
|
*/
|
|
PN_stdfloat GraphicsStateGuardian::
|
|
compute_distance_to(const LPoint3 &point) const {
|
|
switch (_internal_coordinate_system) {
|
|
case CS_zup_right:
|
|
return point[1];
|
|
|
|
case CS_yup_right:
|
|
return -point[2];
|
|
|
|
case CS_zup_left:
|
|
return -point[1];
|
|
|
|
case CS_yup_left:
|
|
return point[2];
|
|
|
|
default:
|
|
gsg_cat.error()
|
|
<< "Invalid coordinate system in compute_distance_to: "
|
|
<< (int)_internal_coordinate_system << "\n";
|
|
return 0.0f;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The gsg contains a large number of useful matrices:
|
|
*
|
|
* * the world transform, * the modelview matrix, * the cs_transform, * etc,
|
|
* etc.
|
|
*
|
|
* A shader can request any of these values, and furthermore, it can request
|
|
* that various compositions, inverses, and transposes be performed. The
|
|
* ShaderMatSpec is a data structure indicating what datum is desired and what
|
|
* conversions to perform. This routine, fetch_specified_value, is
|
|
* responsible for doing the actual retrieval and conversions.
|
|
*
|
|
* Some values, like the following, aren't matrices:
|
|
*
|
|
* * window size * texture coordinates of card center
|
|
*
|
|
* This routine can fetch these values as well, by shoehorning them into a
|
|
* matrix. In this way, we avoid the need for a separate routine to fetch
|
|
* these values.
|
|
*
|
|
* The "altered" bits indicate what parts of the state_and_transform have
|
|
* changed since the last time this particular ShaderMatSpec was evaluated.
|
|
* This may allow data to be cached and not reevaluated.
|
|
*
|
|
*/
|
|
const LMatrix4 *GraphicsStateGuardian::
|
|
fetch_specified_value(Shader::ShaderMatSpec &spec, int altered) {
|
|
LVecBase3 v;
|
|
|
|
if (altered & spec._dep[0]) {
|
|
const LMatrix4 *t = fetch_specified_part(spec._part[0], spec._arg[0], spec._cache[0], spec._index);
|
|
if (t != &spec._cache[0]) {
|
|
spec._cache[0] = *t;
|
|
}
|
|
}
|
|
if (altered & spec._dep[1]) {
|
|
const LMatrix4 *t = fetch_specified_part(spec._part[1], spec._arg[1], spec._cache[1], spec._index);
|
|
if (t != &spec._cache[1]) {
|
|
spec._cache[1] = *t;
|
|
}
|
|
}
|
|
|
|
switch(spec._func) {
|
|
case Shader::SMF_compose:
|
|
spec._value.multiply(spec._cache[0], spec._cache[1]);
|
|
return &spec._value;
|
|
case Shader::SMF_transform_dlight:
|
|
spec._value = spec._cache[0];
|
|
v = spec._cache[1].xform_vec(spec._cache[0].get_row3(2));
|
|
v.normalize();
|
|
spec._value.set_row(2, v);
|
|
v = spec._cache[1].xform_vec(spec._cache[0].get_row3(3));
|
|
v.normalize();
|
|
spec._value.set_row(3, v);
|
|
return &spec._value;
|
|
case Shader::SMF_transform_plight:
|
|
{
|
|
// Careful not to touch the w component, which contains the near value.
|
|
spec._value = spec._cache[0];
|
|
LPoint3 point = spec._cache[1].xform_point(spec._cache[0].get_row3(2));
|
|
spec._value(2, 0) = point[0];
|
|
spec._value(2, 1) = point[1];
|
|
spec._value(2, 2) = point[2];
|
|
return &spec._value;
|
|
}
|
|
case Shader::SMF_transform_slight:
|
|
spec._value = spec._cache[0];
|
|
spec._value.set_row(2, spec._cache[1].xform_point(spec._cache[0].get_row3(2)));
|
|
v = spec._cache[1].xform_vec(spec._cache[0].get_row3(3));
|
|
v.normalize();
|
|
spec._value.set_row(3, v);
|
|
return &spec._value;
|
|
case Shader::SMF_first:
|
|
return &spec._cache[0];
|
|
default:
|
|
// should never get here
|
|
spec._value = LMatrix4::ident_mat();
|
|
return &spec._value;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* See fetch_specified_value
|
|
*/
|
|
const LMatrix4 *GraphicsStateGuardian::
|
|
fetch_specified_part(Shader::ShaderMatInput part, InternalName *name,
|
|
LMatrix4 &t, int index) {
|
|
switch (part) {
|
|
case Shader::SMO_identity: {
|
|
return &LMatrix4::ident_mat();
|
|
}
|
|
case Shader::SMO_window_size: {
|
|
t = LMatrix4::translate_mat(_current_display_region->get_pixel_width(),
|
|
_current_display_region->get_pixel_height(),
|
|
0.0);
|
|
return &t;
|
|
}
|
|
case Shader::SMO_pixel_size: {
|
|
t = LMatrix4::translate_mat(_current_display_region->get_pixel_width(),
|
|
_current_display_region->get_pixel_height(),
|
|
0.0);
|
|
return &t;
|
|
}
|
|
case Shader::SMO_frame_time: {
|
|
PN_stdfloat time = ClockObject::get_global_clock()->get_frame_time();
|
|
t = LMatrix4(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, time, time, time, time);
|
|
return &t;
|
|
}
|
|
case Shader::SMO_frame_delta: {
|
|
PN_stdfloat dt = ClockObject::get_global_clock()->get_dt();
|
|
t = LMatrix4(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, dt, dt, dt, dt);
|
|
return &t;
|
|
}
|
|
case Shader::SMO_texpad_x: {
|
|
Texture *tex = _target_shader->get_shader_input_texture(name);
|
|
nassertr(tex != 0, &LMatrix4::zeros_mat());
|
|
int sx = tex->get_x_size() - tex->get_pad_x_size();
|
|
int sy = tex->get_y_size() - tex->get_pad_y_size();
|
|
int sz = tex->get_z_size() - tex->get_pad_z_size();
|
|
double cx = (sx * 0.5) / tex->get_x_size();
|
|
double cy = (sy * 0.5) / tex->get_y_size();
|
|
double cz = (sz * 0.5) / tex->get_z_size();
|
|
t = LMatrix4(0,0,0,0,0,0,0,0,0,0,0,0,cx,cy,cz,0);
|
|
return &t;
|
|
}
|
|
case Shader::SMO_texpix_x: {
|
|
Texture *tex = _target_shader->get_shader_input_texture(name);
|
|
nassertr(tex != 0, &LMatrix4::zeros_mat());
|
|
double px = 1.0 / tex->get_x_size();
|
|
double py = 1.0 / tex->get_y_size();
|
|
double pz = 1.0 / tex->get_z_size();
|
|
t = LMatrix4(0,0,0,0,0,0,0,0,0,0,0,0,px,py,pz,0);
|
|
return &t;
|
|
}
|
|
case Shader::SMO_attr_material: {
|
|
const MaterialAttrib *target_material = (const MaterialAttrib *)
|
|
_target_rs->get_attrib_def(MaterialAttrib::get_class_slot());
|
|
// Material matrix contains AMBIENT, DIFFUSE, EMISSION, SPECULAR+SHININESS
|
|
if (target_material->is_off()) {
|
|
t = LMatrix4(1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0);
|
|
return &t;
|
|
}
|
|
Material *m = target_material->get_material();
|
|
LVecBase4 const &amb = m->get_ambient();
|
|
LVecBase4 const &dif = m->get_diffuse();
|
|
LVecBase4 const &emm = m->get_emission();
|
|
LVecBase4 spc = m->get_specular();
|
|
spc[3] = m->get_shininess();
|
|
t = LMatrix4(amb[0],amb[1],amb[2],amb[3],
|
|
dif[0],dif[1],dif[2],dif[3],
|
|
emm[0],emm[1],emm[2],emm[3],
|
|
spc[0],spc[1],spc[2],spc[3]);
|
|
return &t;
|
|
}
|
|
case Shader::SMO_attr_material2: {
|
|
const MaterialAttrib *target_material = (const MaterialAttrib *)
|
|
_target_rs->get_attrib_def(MaterialAttrib::get_class_slot());
|
|
if (target_material->is_off()) {
|
|
t = LMatrix4(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1);
|
|
return &t;
|
|
}
|
|
Material *m = target_material->get_material();
|
|
t.set_row(0, m->get_base_color());
|
|
t.set_row(3, LVecBase4(m->get_metallic(), m->get_refractive_index(), 0, m->get_roughness()));
|
|
return &t;
|
|
}
|
|
case Shader::SMO_attr_color: {
|
|
const ColorAttrib *target_color = (const ColorAttrib *)
|
|
_target_rs->get_attrib_def(ColorAttrib::get_class_slot());
|
|
if (target_color->get_color_type() != ColorAttrib::T_flat) {
|
|
return &LMatrix4::ones_mat();
|
|
}
|
|
LVecBase4 c = target_color->get_color();
|
|
t = LMatrix4(0,0,0,0,0,0,0,0,0,0,0,0,c[0],c[1],c[2],c[3]);
|
|
return &t;
|
|
}
|
|
case Shader::SMO_attr_colorscale: {
|
|
const ColorScaleAttrib *target_color = (const ColorScaleAttrib *)
|
|
_target_rs->get_attrib_def(ColorScaleAttrib::get_class_slot());
|
|
if (target_color->is_identity()) {
|
|
return &LMatrix4::ones_mat();
|
|
}
|
|
LVecBase4 cs = target_color->get_scale();
|
|
t = LMatrix4(0,0,0,0,0,0,0,0,0,0,0,0,cs[0],cs[1],cs[2],cs[3]);
|
|
return &t;
|
|
}
|
|
case Shader::SMO_attr_fog: {
|
|
const FogAttrib *target_fog = (const FogAttrib *)
|
|
_target_rs->get_attrib_def(FogAttrib::get_class_slot());
|
|
Fog *fog = target_fog->get_fog();
|
|
if (fog == (Fog*) NULL) {
|
|
return &LMatrix4::ones_mat();
|
|
}
|
|
PN_stdfloat start, end;
|
|
fog->get_linear_range(start, end);
|
|
t = LMatrix4(0,0,0,0,0,0,0,0,0,0,0,0,fog->get_exp_density(),start,end,1.0f/(end-start));
|
|
return &t;
|
|
}
|
|
case Shader::SMO_attr_fogcolor: {
|
|
const FogAttrib *target_fog = (const FogAttrib *)
|
|
_target_rs->get_attrib_def(FogAttrib::get_class_slot());
|
|
Fog *fog = target_fog->get_fog();
|
|
if (fog == (Fog*) NULL) {
|
|
return &LMatrix4::ones_mat();
|
|
}
|
|
LVecBase4 c = fog->get_color();
|
|
t = LMatrix4(0,0,0,0,0,0,0,0,0,0,0,0,c[0],c[1],c[2],c[3]);
|
|
return &t;
|
|
}
|
|
case Shader::SMO_alight_x: {
|
|
const NodePath &np = _target_shader->get_shader_input_nodepath(name);
|
|
nassertr(!np.is_empty(), &LMatrix4::zeros_mat());
|
|
AmbientLight *lt;
|
|
DCAST_INTO_R(lt, np.node(), &LMatrix4::zeros_mat());
|
|
LColor const &c = lt->get_color();
|
|
t = LMatrix4(0,0,0,0,0,0,0,0,0,0,0,0,c[0],c[1],c[2],c[3]);
|
|
return &t;
|
|
}
|
|
case Shader::SMO_satten_x: {
|
|
const NodePath &np = _target_shader->get_shader_input_nodepath(name);
|
|
nassertr(!np.is_empty(), &LMatrix4::ones_mat());
|
|
Spotlight *lt;
|
|
DCAST_INTO_R(lt, np.node(), &LMatrix4::ones_mat());
|
|
LVecBase3 const &a = lt->get_attenuation();
|
|
PN_stdfloat x = lt->get_exponent();
|
|
t = LMatrix4(0,0,0,0,0,0,0,0,0,0,0,0,a[0],a[1],a[2],x);
|
|
return &t;
|
|
}
|
|
case Shader::SMO_dlight_x: {
|
|
// The dlight matrix contains COLOR, SPECULAR, DIRECTION, PSEUDOHALFANGLE
|
|
const NodePath &np = _target_shader->get_shader_input_nodepath(name);
|
|
nassertr(!np.is_empty(), &LMatrix4::zeros_mat());
|
|
DirectionalLight *lt;
|
|
DCAST_INTO_R(lt, np.node(), &LMatrix4::zeros_mat());
|
|
LColor const &c = lt->get_color();
|
|
LColor const &s = lt->get_specular_color();
|
|
t = np.get_net_transform()->get_mat() *
|
|
get_scene()->get_world_transform()->get_mat();
|
|
LVecBase3 d = -(t.xform_vec(lt->get_direction()));
|
|
d.normalize();
|
|
LVecBase3 h = d + LVecBase3(0,-1,0);
|
|
h.normalize();
|
|
t = LMatrix4(c[0],c[1],c[2],c[3],s[0],s[1],s[2],c[3],d[0],d[1],d[2],0,h[0],h[1],h[2],0);
|
|
return &t;
|
|
}
|
|
case Shader::SMO_plight_x: {
|
|
// The plight matrix contains COLOR, SPECULAR, POINT, ATTENUATION
|
|
const NodePath &np = _target_shader->get_shader_input_nodepath(name);
|
|
nassertr(!np.is_empty(), &LMatrix4::ones_mat());
|
|
PointLight *lt;
|
|
DCAST_INTO_R(lt, np.node(), &LMatrix4::zeros_mat());
|
|
LColor const &c = lt->get_color();
|
|
LColor const &s = lt->get_specular_color();
|
|
t = np.get_net_transform()->get_mat() *
|
|
get_scene()->get_world_transform()->get_mat();
|
|
LVecBase3 p = (t.xform_point(lt->get_point()));
|
|
LVecBase3 a = lt->get_attenuation();
|
|
PN_stdfloat lnear = lt->get_lens(0)->get_near();
|
|
PN_stdfloat lfar = lt->get_lens(0)->get_far();
|
|
t = LMatrix4(c[0],c[1],c[2],c[3],s[0],s[1],s[2],s[3],p[0],p[1],p[2],lnear,a[0],a[1],a[2],lfar);
|
|
return &t;
|
|
}
|
|
case Shader::SMO_slight_x: {
|
|
// The slight matrix contains COLOR, SPECULAR, POINT, DIRECTION
|
|
const NodePath &np = _target_shader->get_shader_input_nodepath(name);
|
|
nassertr(!np.is_empty(), &LMatrix4::zeros_mat());
|
|
Spotlight *lt;
|
|
DCAST_INTO_R(lt, np.node(), &LMatrix4::zeros_mat());
|
|
Lens *lens = lt->get_lens();
|
|
nassertr(lens != (Lens *)NULL, &LMatrix4::zeros_mat());
|
|
LColor const &c = lt->get_color();
|
|
LColor const &s = lt->get_specular_color();
|
|
PN_stdfloat cutoff = ccos(deg_2_rad(lens->get_hfov() * 0.5f));
|
|
t = np.get_net_transform()->get_mat() *
|
|
get_scene()->get_world_transform()->get_mat();
|
|
LVecBase3 p = t.xform_point(lens->get_nodal_point());
|
|
LVecBase3 d = -(t.xform_vec(lens->get_view_vector()));
|
|
t = LMatrix4(c[0],c[1],c[2],c[3],s[0],s[1],s[2],s[3],p[0],p[1],p[2],0,d[0],d[1],d[2],cutoff);
|
|
return &t;
|
|
}
|
|
case Shader::SMO_light_ambient: {
|
|
LColor cur_ambient_light(0.0f, 0.0f, 0.0f, 0.0f);
|
|
const LightAttrib *target_light = (const LightAttrib *)
|
|
_target_rs->get_attrib_def(LightAttrib::get_class_slot());
|
|
|
|
int num_on_lights = target_light->get_num_on_lights();
|
|
if (num_on_lights == 0) {
|
|
// There are no lights at all. This means, to follow the fixed-
|
|
// function model, we pretend there is an all-white ambient light.
|
|
t.set_row(3, LVecBase4(1, 1, 1, 1));
|
|
} else {
|
|
for (int li = 0; li < num_on_lights; li++) {
|
|
NodePath light = target_light->get_on_light(li);
|
|
nassertr(!light.is_empty(), &LMatrix4::zeros_mat());
|
|
Light *light_obj = light.node()->as_light();
|
|
nassertr(light_obj != (Light *)NULL, &LMatrix4::zeros_mat());
|
|
|
|
if (light_obj->get_type() == AmbientLight::get_class_type()) {
|
|
cur_ambient_light += light_obj->get_color();
|
|
}
|
|
}
|
|
t.set_row(3, cur_ambient_light);
|
|
}
|
|
return &t;
|
|
}
|
|
case Shader::SMO_texmat_i: {
|
|
const TexMatrixAttrib *tma;
|
|
const TextureAttrib *ta;
|
|
if (_target_rs->get_attrib(ta) && _target_rs->get_attrib(tma) &&
|
|
index < ta->get_num_on_stages()) {
|
|
return &tma->get_mat(ta->get_on_stage(index));
|
|
} else {
|
|
return &LMatrix4::ident_mat();
|
|
}
|
|
}
|
|
case Shader::SMO_inv_texmat_i: {
|
|
const TexMatrixAttrib *tma;
|
|
const TextureAttrib *ta;
|
|
if (_target_rs->get_attrib(ta) && _target_rs->get_attrib(tma) &&
|
|
index < ta->get_num_on_stages()) {
|
|
t = tma->get_transform(ta->get_on_stage(index))->get_inverse()->get_mat();
|
|
return &t;
|
|
} else {
|
|
return &LMatrix4::ident_mat();
|
|
}
|
|
}
|
|
case Shader::SMO_tex_is_alpha_i: {
|
|
// This is a hack so we can support both F_alpha and other formats in the
|
|
// default shader, to fix font rendering in GLES2
|
|
const TextureAttrib *ta;
|
|
if (_target_rs->get_attrib(ta) &&
|
|
index < ta->get_num_on_stages()) {
|
|
TextureStage *ts = ta->get_on_stage(index);
|
|
PN_stdfloat v = (ta->get_on_texture(ts)->get_format() == Texture::F_alpha);
|
|
t = LMatrix4(0,0,0,0,0,0,0,0,0,0,0,0,v,v,v,0);
|
|
return &t;
|
|
} else {
|
|
return &LMatrix4::zeros_mat();
|
|
}
|
|
}
|
|
case Shader::SMO_plane_x: {
|
|
const NodePath &np = _target_shader->get_shader_input_nodepath(name);
|
|
nassertr(!np.is_empty(), &LMatrix4::zeros_mat());
|
|
nassertr(np.node()->is_of_type(PlaneNode::get_class_type()), &LMatrix4::zeros_mat());
|
|
LPlane p = DCAST(PlaneNode, np.node())->get_plane();
|
|
t = LMatrix4(0,0,0,0,0,0,0,0,0,0,0,0,p[0],p[1],p[2],p[3]);
|
|
return &t;
|
|
}
|
|
case Shader::SMO_clipplane_x: {
|
|
const ClipPlaneAttrib *cpa = DCAST(ClipPlaneAttrib, _target_rs->get_attrib_def(ClipPlaneAttrib::get_class_slot()));
|
|
int planenr = atoi(name->get_name().c_str());
|
|
if (planenr >= cpa->get_num_on_planes()) {
|
|
return &LMatrix4::zeros_mat();
|
|
}
|
|
const NodePath &np = cpa->get_on_plane(planenr);
|
|
nassertr(!np.is_empty(), &LMatrix4::zeros_mat());
|
|
nassertr(np.node()->is_of_type(PlaneNode::get_class_type()), &LMatrix4::zeros_mat());
|
|
LPlane p (DCAST(PlaneNode, np.node())->get_plane());
|
|
p.xform(np.get_net_transform()->get_mat()); // World-space
|
|
t = LMatrix4(0,0,0,0,0,0,0,0,0,0,0,0,p[0],p[1],p[2],p[3]);
|
|
return &t;
|
|
}
|
|
case Shader::SMO_apiview_clipplane_i: {
|
|
const ClipPlaneAttrib *cpa = DCAST(ClipPlaneAttrib, _target_rs->get_attrib_def(ClipPlaneAttrib::get_class_slot()));
|
|
if (index >= cpa->get_num_on_planes()) {
|
|
return &LMatrix4::zeros_mat();
|
|
}
|
|
|
|
const NodePath &plane = cpa->get_on_plane(index);
|
|
nassertr(!plane.is_empty(), &LMatrix4::zeros_mat());
|
|
const PlaneNode *plane_node;
|
|
DCAST_INTO_R(plane_node, plane.node(), &LMatrix4::zeros_mat());
|
|
|
|
CPT(TransformState) transform =
|
|
get_scene()->get_cs_world_transform()->compose(
|
|
plane.get_transform(_scene_setup->get_scene_root().get_parent()));
|
|
|
|
LPlane xformed_plane = plane_node->get_plane() * transform->get_mat();
|
|
t.set_row(3, xformed_plane);
|
|
return &t;
|
|
}
|
|
case Shader::SMO_mat_constant_x: {
|
|
return &_target_shader->get_shader_input_matrix(name, t);
|
|
}
|
|
case Shader::SMO_vec_constant_x: {
|
|
const LVecBase4 &input = _target_shader->get_shader_input_vector(name);
|
|
const PN_stdfloat *data = input.get_data();
|
|
t = LMatrix4(data[0],data[1],data[2],data[3],
|
|
data[0],data[1],data[2],data[3],
|
|
data[0],data[1],data[2],data[3],
|
|
data[0],data[1],data[2],data[3]);
|
|
return &t;
|
|
}
|
|
case Shader::SMO_world_to_view: {
|
|
return &(get_scene()->get_world_transform()->get_mat());
|
|
break;
|
|
}
|
|
case Shader::SMO_view_to_world: {
|
|
return &(get_scene()->get_camera_transform()->get_mat());
|
|
}
|
|
case Shader::SMO_model_to_view: {
|
|
return &(get_external_transform()->get_mat());
|
|
}
|
|
case Shader::SMO_model_to_apiview: {
|
|
return &(get_internal_transform()->get_mat());
|
|
}
|
|
case Shader::SMO_view_to_model: {
|
|
t = get_external_transform()->get_inverse()->get_mat();
|
|
return &t;
|
|
}
|
|
case Shader::SMO_apiview_to_model: {
|
|
t = get_internal_transform()->get_inverse()->get_mat();
|
|
return &t;
|
|
}
|
|
case Shader::SMO_apiview_to_view: {
|
|
return &(_inv_cs_transform->get_mat());
|
|
}
|
|
case Shader::SMO_view_to_apiview: {
|
|
return &(_cs_transform->get_mat());
|
|
}
|
|
case Shader::SMO_clip_to_view: {
|
|
if (_current_lens->get_coordinate_system() == _coordinate_system) {
|
|
return &(_current_lens->get_projection_mat_inv(_current_stereo_channel));
|
|
} else {
|
|
t = _current_lens->get_projection_mat_inv(_current_stereo_channel) *
|
|
LMatrix4::convert_mat(_current_lens->get_coordinate_system(), _coordinate_system);
|
|
return &t;
|
|
}
|
|
}
|
|
case Shader::SMO_view_to_clip: {
|
|
if (_current_lens->get_coordinate_system() == _coordinate_system) {
|
|
return &(_current_lens->get_projection_mat(_current_stereo_channel));
|
|
} else {
|
|
t = LMatrix4::convert_mat(_coordinate_system, _current_lens->get_coordinate_system()) *
|
|
_current_lens->get_projection_mat(_current_stereo_channel);
|
|
return &t;
|
|
}
|
|
}
|
|
case Shader::SMO_apiclip_to_view: {
|
|
t = _projection_mat_inv->get_mat() * _inv_cs_transform->get_mat();
|
|
return &t;
|
|
}
|
|
case Shader::SMO_view_to_apiclip: {
|
|
t = _cs_transform->get_mat() * _projection_mat->get_mat();
|
|
return &t;
|
|
}
|
|
case Shader::SMO_apiclip_to_apiview: {
|
|
return &(_projection_mat_inv->get_mat());
|
|
}
|
|
case Shader::SMO_apiview_to_apiclip: {
|
|
return &(_projection_mat->get_mat());
|
|
}
|
|
case Shader::SMO_view_x_to_view: {
|
|
const NodePath &np = _target_shader->get_shader_input_nodepath(name);
|
|
nassertr(!np.is_empty(), &LMatrix4::ident_mat());
|
|
t = np.get_net_transform()->get_mat() *
|
|
get_scene()->get_world_transform()->get_mat();
|
|
return &t;
|
|
}
|
|
case Shader::SMO_view_to_view_x: {
|
|
const NodePath &np = _target_shader->get_shader_input_nodepath(name);
|
|
nassertr(!np.is_empty(), &LMatrix4::ident_mat());
|
|
t = get_scene()->get_camera_transform()->get_mat() *
|
|
np.get_net_transform()->get_inverse()->get_mat();
|
|
return &t;
|
|
}
|
|
case Shader::SMO_apiview_x_to_view: {
|
|
const NodePath &np = _target_shader->get_shader_input_nodepath(name);
|
|
nassertr(!np.is_empty(), &LMatrix4::ident_mat());
|
|
t = LMatrix4::convert_mat(_internal_coordinate_system, _coordinate_system) *
|
|
np.get_net_transform()->get_mat() *
|
|
get_scene()->get_world_transform()->get_mat();
|
|
return &t;
|
|
}
|
|
case Shader::SMO_view_to_apiview_x: {
|
|
const NodePath &np = _target_shader->get_shader_input_nodepath(name);
|
|
nassertr(!np.is_empty(), &LMatrix4::ident_mat());
|
|
t = (get_scene()->get_camera_transform()->get_mat() *
|
|
np.get_net_transform()->get_inverse()->get_mat() *
|
|
LMatrix4::convert_mat(_coordinate_system, _internal_coordinate_system));
|
|
return &t;
|
|
}
|
|
case Shader::SMO_clip_x_to_view: {
|
|
const NodePath &np = _target_shader->get_shader_input_nodepath(name);
|
|
nassertr(!np.is_empty(), &LMatrix4::ident_mat());
|
|
nassertr(np.node()->is_of_type(LensNode::get_class_type()), &LMatrix4::ident_mat());
|
|
Lens *lens = DCAST(LensNode, np.node())->get_lens();
|
|
t = lens->get_projection_mat_inv(_current_stereo_channel) *
|
|
LMatrix4::convert_mat(lens->get_coordinate_system(), _coordinate_system) *
|
|
np.get_net_transform()->get_mat() *
|
|
get_scene()->get_world_transform()->get_mat();
|
|
return &t;
|
|
}
|
|
case Shader::SMO_view_to_clip_x: {
|
|
const NodePath &np = _target_shader->get_shader_input_nodepath(name);
|
|
nassertr(!np.is_empty(), &LMatrix4::ident_mat());
|
|
nassertr(np.node()->is_of_type(LensNode::get_class_type()), &LMatrix4::ident_mat());
|
|
Lens *lens = DCAST(LensNode, np.node())->get_lens();
|
|
t = get_scene()->get_camera_transform()->get_mat() *
|
|
np.get_net_transform()->get_inverse()->get_mat() *
|
|
LMatrix4::convert_mat(_coordinate_system, lens->get_coordinate_system()) *
|
|
lens->get_projection_mat(_current_stereo_channel);
|
|
return &t;
|
|
}
|
|
case Shader::SMO_apiclip_x_to_view: {
|
|
const NodePath &np = _target_shader->get_shader_input_nodepath(name);
|
|
nassertr(!np.is_empty(), &LMatrix4::ident_mat());
|
|
nassertr(np.node()->is_of_type(LensNode::get_class_type()), &LMatrix4::ident_mat());
|
|
Lens *lens = DCAST(LensNode, np.node())->get_lens();
|
|
t = calc_projection_mat(lens)->get_inverse()->get_mat() *
|
|
get_cs_transform_for(lens->get_coordinate_system())->get_inverse()->get_mat() *
|
|
np.get_net_transform()->get_mat() *
|
|
get_scene()->get_world_transform()->get_mat();
|
|
return &t;
|
|
}
|
|
case Shader::SMO_view_to_apiclip_x: {
|
|
const NodePath &np = _target_shader->get_shader_input_nodepath(name);
|
|
nassertr(!np.is_empty(), &LMatrix4::ident_mat());
|
|
nassertr(np.node()->is_of_type(LensNode::get_class_type()), &LMatrix4::ident_mat());
|
|
Lens *lens = DCAST(LensNode, np.node())->get_lens();
|
|
t = get_scene()->get_camera_transform()->get_mat() *
|
|
np.get_net_transform()->get_inverse()->get_mat() *
|
|
get_cs_transform_for(lens->get_coordinate_system())->get_mat() *
|
|
calc_projection_mat(lens)->get_mat();
|
|
return &t;
|
|
}
|
|
case Shader::SMO_mat_constant_x_attrib: {
|
|
if (_target_shader->has_shader_input(name)) {
|
|
// There is an input specifying precisely this whole thing, with dot and
|
|
// all. Support this, even if only for backward compatibility.
|
|
return &_target_shader->get_shader_input_matrix(name, t);
|
|
}
|
|
|
|
const NodePath &np = _target_shader->get_shader_input_nodepath(name->get_parent());
|
|
nassertr(!np.is_empty(), &LMatrix4::ident_mat());
|
|
|
|
return fetch_specified_member(np, name->get_basename(), t);
|
|
}
|
|
case Shader::SMO_vec_constant_x_attrib: {
|
|
if (_target_shader->has_shader_input(name)) {
|
|
// There is an input specifying precisely this whole thing, with dot and
|
|
// all. Support this, even if only for backward compatibility.
|
|
const LVecBase4 &data = _target_shader->get_shader_input_vector(name);
|
|
t = LMatrix4(data[0],data[1],data[2],data[3],
|
|
data[0],data[1],data[2],data[3],
|
|
data[0],data[1],data[2],data[3],
|
|
data[0],data[1],data[2],data[3]);
|
|
return &t;
|
|
}
|
|
|
|
const NodePath &np = _target_shader->get_shader_input_nodepath(name->get_parent());
|
|
nassertr(!np.is_empty(), &LMatrix4::ident_mat());
|
|
|
|
return fetch_specified_member(np, name->get_basename(), t);
|
|
}
|
|
case Shader::SMO_light_source_i_attrib: {
|
|
const LightAttrib *target_light;
|
|
_target_rs->get_attrib_def(target_light);
|
|
|
|
// We want to ignore ambient lights. To that effect, iterate through the
|
|
// list of lights. In the future, we will improve this system, by also
|
|
// filtering down to the number of lights specified by the shader.
|
|
int i = 0;
|
|
|
|
int num_on_lights = target_light->get_num_on_lights();
|
|
for (int li = 0; li < num_on_lights; li++) {
|
|
NodePath light = target_light->get_on_light(li);
|
|
nassertr(!light.is_empty(), &LMatrix4::ident_mat());
|
|
Light *light_obj = light.node()->as_light();
|
|
nassertr(light_obj != (Light *)NULL, &LMatrix4::ident_mat());
|
|
|
|
if (light_obj->get_type() != AmbientLight::get_class_type()) {
|
|
if (i++ == index) {
|
|
return fetch_specified_member(light, name, t);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apply the default OpenGL lights otherwise.
|
|
// Special exception for light 0, which defaults to white.
|
|
if (index == 0) {
|
|
string basename = name->get_basename();
|
|
if (basename == "color" || basename == "diffuse") {
|
|
t.set_row(3, _light_color_scale);
|
|
return &t;
|
|
} else if (basename == "specular") {
|
|
return &LMatrix4::ones_mat();
|
|
}
|
|
}
|
|
return fetch_specified_member(NodePath(), name, t);
|
|
}
|
|
default:
|
|
nassertr(false /*should never get here*/, &LMatrix4::ident_mat());
|
|
return &LMatrix4::ident_mat();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Given a NodePath passed into a shader input that is a structure, fetches
|
|
* the value for the given member.
|
|
*/
|
|
const LMatrix4 *GraphicsStateGuardian::
|
|
fetch_specified_member(const NodePath &np, CPT_InternalName attrib, LMatrix4 &t) {
|
|
// This system is not ideal. It will be improved in the future.
|
|
static const CPT_InternalName IN_color("color");
|
|
static const CPT_InternalName IN_ambient("ambient");
|
|
static const CPT_InternalName IN_diffuse("diffuse");
|
|
static const CPT_InternalName IN_specular("specular");
|
|
static const CPT_InternalName IN_position("position");
|
|
static const CPT_InternalName IN_halfVector("halfVector");
|
|
static const CPT_InternalName IN_spotDirection("spotDirection");
|
|
static const CPT_InternalName IN_spotCutoff("spotCutoff");
|
|
static const CPT_InternalName IN_spotCosCutoff("spotCosCutoff");
|
|
static const CPT_InternalName IN_spotExponent("spotExponent");
|
|
static const CPT_InternalName IN_attenuation("attenuation");
|
|
static const CPT_InternalName IN_constantAttenuation("constantAttenuation");
|
|
static const CPT_InternalName IN_linearAttenuation("linearAttenuation");
|
|
static const CPT_InternalName IN_quadraticAttenuation("quadraticAttenuation");
|
|
static const CPT_InternalName IN_shadowMatrix("shadowMatrix");
|
|
|
|
PandaNode *node = NULL;
|
|
if (!np.is_empty()) {
|
|
node = np.node();
|
|
}
|
|
|
|
if (attrib == IN_color) {
|
|
if (node == (PandaNode *)NULL) {
|
|
return &LMatrix4::ident_mat();
|
|
}
|
|
Light *light = node->as_light();
|
|
nassertr(light != (Light *)NULL, &LMatrix4::ident_mat());
|
|
LColor c = light->get_color();
|
|
c.componentwise_mult(_light_color_scale);
|
|
t.set_row(3, c);
|
|
return &t;
|
|
|
|
} else if (attrib == IN_ambient) {
|
|
if (node == (PandaNode *)NULL) {
|
|
return &LMatrix4::ident_mat();
|
|
}
|
|
Light *light = node->as_light();
|
|
nassertr(light != (Light *)NULL, &LMatrix4::ident_mat());
|
|
if (node->is_of_type(AmbientLight::get_class_type())) {
|
|
LColor c = light->get_color();
|
|
c.componentwise_mult(_light_color_scale);
|
|
t.set_row(3, c);
|
|
} else {
|
|
// Non-ambient lights don't currently have an ambient color in Panda3D.
|
|
t.set_row(3, LColor(0.0f, 0.0f, 0.0f, 1.0f));
|
|
}
|
|
return &t;
|
|
|
|
} else if (attrib == IN_diffuse) {
|
|
if (node == (PandaNode *)NULL) {
|
|
return &LMatrix4::ident_mat();
|
|
}
|
|
Light *light = node->as_light();
|
|
nassertr(light != (Light *)NULL, &LMatrix4::ones_mat());
|
|
if (node->is_of_type(AmbientLight::get_class_type())) {
|
|
// Ambient light has no diffuse color.
|
|
t.set_row(3, LColor(0.0f, 0.0f, 0.0f, 1.0f));
|
|
} else {
|
|
LColor c = light->get_color();
|
|
c.componentwise_mult(_light_color_scale);
|
|
t.set_row(3, c);
|
|
}
|
|
return &t;
|
|
|
|
} else if (attrib == IN_specular) {
|
|
if (node == (PandaNode *)NULL) {
|
|
return &LMatrix4::ident_mat();
|
|
}
|
|
Light *light = node->as_light();
|
|
nassertr(light != (Light *)NULL, &LMatrix4::ones_mat());
|
|
t.set_row(3, light->get_specular_color());
|
|
return &t;
|
|
|
|
} else if (attrib == IN_position) {
|
|
if (np.is_empty()) {
|
|
t = LMatrix4(0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0);
|
|
return &t;
|
|
} else if (node->is_of_type(AmbientLight::get_class_type())) {
|
|
// Ambient light has no position.
|
|
t = LMatrix4(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
|
|
return &t;
|
|
} else if (node->is_of_type(DirectionalLight::get_class_type())) {
|
|
DirectionalLight *light;
|
|
DCAST_INTO_R(light, node, &LMatrix4::ident_mat());
|
|
|
|
CPT(TransformState) transform = np.get_transform(_scene_setup->get_scene_root().get_parent());
|
|
LVector3 dir = -(light->get_direction() * transform->get_mat());
|
|
dir *= get_scene()->get_cs_world_transform()->get_mat();
|
|
t = LMatrix4(0,0,0,0,0,0,0,0,0,0,0,0,dir[0],dir[1],dir[2],0);
|
|
return &t;
|
|
} else {
|
|
LightLensNode *light;
|
|
DCAST_INTO_R(light, node, &LMatrix4::ident_mat());
|
|
Lens *lens = light->get_lens();
|
|
nassertr(lens != (Lens *)NULL, &LMatrix4::ident_mat());
|
|
|
|
CPT(TransformState) transform =
|
|
get_scene()->get_cs_world_transform()->compose(
|
|
np.get_transform(_scene_setup->get_scene_root().get_parent()));
|
|
|
|
const LMatrix4 &light_mat = transform->get_mat();
|
|
LPoint3 pos = lens->get_nodal_point() * light_mat;
|
|
t = LMatrix4::translate_mat(pos);
|
|
return &t;
|
|
}
|
|
|
|
} else if (attrib == IN_halfVector) {
|
|
if (np.is_empty()) {
|
|
t = LMatrix4(0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0);
|
|
return &t;
|
|
} else if (node->is_of_type(AmbientLight::get_class_type())) {
|
|
// Ambient light has no half-vector.
|
|
t = LMatrix4(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
|
|
return &t;
|
|
} else if (node->is_of_type(DirectionalLight::get_class_type())) {
|
|
DirectionalLight *light;
|
|
DCAST_INTO_R(light, node, &LMatrix4::ident_mat());
|
|
|
|
CPT(TransformState) transform = np.get_transform(_scene_setup->get_scene_root().get_parent());
|
|
LVector3 dir = -(light->get_direction() * transform->get_mat());
|
|
dir *= get_scene()->get_cs_world_transform()->get_mat();
|
|
dir.normalize();
|
|
dir += LVector3(0, 0, 1);
|
|
dir.normalize();
|
|
t = LMatrix4(0,0,0,0,0,0,0,0,0,0,0,0,dir[0],dir[1],dir[2],1);
|
|
return &t;
|
|
} else {
|
|
LightLensNode *light;
|
|
DCAST_INTO_R(light, node, &LMatrix4::ident_mat());
|
|
Lens *lens = light->get_lens();
|
|
nassertr(lens != (Lens *)NULL, &LMatrix4::ident_mat());
|
|
|
|
CPT(TransformState) transform =
|
|
get_scene()->get_cs_world_transform()->compose(
|
|
np.get_transform(_scene_setup->get_scene_root().get_parent()));
|
|
|
|
const LMatrix4 &light_mat = transform->get_mat();
|
|
LPoint3 pos = lens->get_nodal_point() * light_mat;
|
|
pos.normalize();
|
|
pos += LVector3(0, 0, 1);
|
|
pos.normalize();
|
|
t = LMatrix4(0,0,0,0,0,0,0,0,0,0,0,0,pos[0],pos[1],pos[2],1);
|
|
return &t;
|
|
}
|
|
|
|
} else if (attrib == IN_spotDirection) {
|
|
if (node == (PandaNode *)NULL) {
|
|
t.set_row(3, LVector3(0.0f, 0.0f, -1.0f));
|
|
return &t;
|
|
} else if (node->is_of_type(AmbientLight::get_class_type())) {
|
|
// Ambient light has no spot direction.
|
|
t.set_row(3, LVector3(0.0f, 0.0f, 0.0f));
|
|
return &t;
|
|
} else {
|
|
LightLensNode *light;
|
|
DCAST_INTO_R(light, node, &LMatrix4::ident_mat());
|
|
Lens *lens = light->get_lens();
|
|
nassertr(lens != (Lens *)NULL, &LMatrix4::ident_mat());
|
|
|
|
CPT(TransformState) transform =
|
|
get_scene()->get_cs_world_transform()->compose(
|
|
np.get_transform(_scene_setup->get_scene_root().get_parent()));
|
|
|
|
const LMatrix4 &light_mat = transform->get_mat();
|
|
LVector3 dir = lens->get_view_vector() * light_mat;
|
|
t.set_row(3, dir);
|
|
return &t;
|
|
}
|
|
|
|
} else if (attrib == IN_spotCutoff) {
|
|
if (node != (PandaNode *)NULL &&
|
|
node->is_of_type(Spotlight::get_class_type())) {
|
|
LightLensNode *light;
|
|
DCAST_INTO_R(light, node, &LMatrix4::ident_mat());
|
|
Lens *lens = light->get_lens();
|
|
nassertr(lens != (Lens *)NULL, &LMatrix4::ident_mat());
|
|
|
|
float cutoff = lens->get_hfov() * 0.5f;
|
|
t.set_row(3, LVecBase4(cutoff));
|
|
return &t;
|
|
} else {
|
|
// Other lights have no cut-off.
|
|
t.set_row(3, LVecBase4(180));
|
|
return &t;
|
|
}
|
|
|
|
} else if (attrib == IN_spotCosCutoff) {
|
|
if (node != (PandaNode *)NULL &&
|
|
node->is_of_type(Spotlight::get_class_type())) {
|
|
LightLensNode *light;
|
|
DCAST_INTO_R(light, node, &LMatrix4::ident_mat());
|
|
Lens *lens = light->get_lens();
|
|
nassertr(lens != (Lens *)NULL, &LMatrix4::ident_mat());
|
|
|
|
float cutoff = lens->get_hfov() * 0.5f;
|
|
t.set_row(3, LVecBase4(ccos(deg_2_rad(cutoff))));
|
|
return &t;
|
|
} else {
|
|
// Other lights have no cut-off.
|
|
t.set_row(3, LVecBase4(-1));
|
|
return &t;
|
|
}
|
|
|
|
} else if (attrib == IN_spotExponent) {
|
|
if (node == (PandaNode *)NULL) {
|
|
return &LMatrix4::zeros_mat();
|
|
}
|
|
Light *light = node->as_light();
|
|
nassertr(light != (Light *)NULL, &LMatrix4::ident_mat());
|
|
|
|
t.set_row(3, LVecBase4(light->get_exponent()));
|
|
return &t;
|
|
|
|
} else if (attrib == IN_attenuation) {
|
|
if (node != (PandaNode *)NULL) {
|
|
Light *light = node->as_light();
|
|
nassertr(light != (Light *)NULL, &LMatrix4::ones_mat());
|
|
|
|
t.set_row(3, LVecBase4(light->get_attenuation(), 0));
|
|
} else {
|
|
t.set_row(3, LVecBase4(1, 0, 0, 0));
|
|
}
|
|
return &t;
|
|
|
|
} else if (attrib == IN_constantAttenuation) {
|
|
if (node == (PandaNode *)NULL) {
|
|
return &LMatrix4::ones_mat();
|
|
}
|
|
Light *light = node->as_light();
|
|
nassertr(light != (Light *)NULL, &LMatrix4::ones_mat());
|
|
|
|
t.set_row(3, LVecBase4(light->get_attenuation()[0]));
|
|
return &t;
|
|
|
|
} else if (attrib == IN_linearAttenuation) {
|
|
if (node == (PandaNode *)NULL) {
|
|
return &LMatrix4::zeros_mat();
|
|
}
|
|
Light *light = node->as_light();
|
|
nassertr(light != (Light *)NULL, &LMatrix4::ident_mat());
|
|
|
|
t.set_row(3, LVecBase4(light->get_attenuation()[1]));
|
|
return &t;
|
|
|
|
} else if (attrib == IN_quadraticAttenuation) {
|
|
if (node == (PandaNode *)NULL) {
|
|
return &LMatrix4::zeros_mat();
|
|
}
|
|
Light *light = node->as_light();
|
|
nassertr(light != (Light *)NULL, &LMatrix4::ident_mat());
|
|
|
|
t.set_row(3, LVecBase4(light->get_attenuation()[2]));
|
|
return &t;
|
|
|
|
} else if (attrib == IN_shadowMatrix) {
|
|
static const LMatrix4 biasmat(0.5f, 0.0f, 0.0f, 0.0f,
|
|
0.0f, 0.5f, 0.0f, 0.0f,
|
|
0.0f, 0.0f, 0.5f, 0.0f,
|
|
0.5f, 0.5f, 0.5f, 1.0f);
|
|
|
|
if (node == (PandaNode *)NULL) {
|
|
return &biasmat;
|
|
}
|
|
|
|
LensNode *lnode;
|
|
DCAST_INTO_R(lnode, node, &LMatrix4::ident_mat());
|
|
Lens *lens = lnode->get_lens();
|
|
|
|
t = get_external_transform()->get_mat() *
|
|
get_scene()->get_camera_transform()->get_mat() *
|
|
np.get_net_transform()->get_inverse()->get_mat() *
|
|
LMatrix4::convert_mat(_coordinate_system, lens->get_coordinate_system());
|
|
|
|
if (!node->is_of_type(PointLight::get_class_type())) {
|
|
t *= lens->get_projection_mat() * biasmat;
|
|
}
|
|
return &t;
|
|
|
|
} else {
|
|
display_cat.error()
|
|
<< "Shader input requests invalid attribute " << *attrib
|
|
<< " from node " << np << "\n";
|
|
return &LMatrix4::ident_mat();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Like fetch_specified_value, but for texture inputs.
|
|
*/
|
|
PT(Texture) GraphicsStateGuardian::
|
|
fetch_specified_texture(Shader::ShaderTexSpec &spec, SamplerState &sampler,
|
|
int &view) {
|
|
switch (spec._part) {
|
|
case Shader::STO_named_input:
|
|
// Named texture input.
|
|
if (!_target_shader->has_shader_input(spec._name)) {
|
|
// Is this a member of something, like a node?
|
|
const InternalName *parent = spec._name->get_parent();
|
|
if (parent != InternalName::get_root() &&
|
|
_target_shader->has_shader_input(parent)) {
|
|
|
|
// Yes, grab the node.
|
|
const string &basename = spec._name->get_basename();
|
|
NodePath np = _target_shader->get_shader_input_nodepath(parent);
|
|
|
|
if (basename == "shadowMap") {
|
|
PT(Texture) tex = get_shadow_map(np);
|
|
if (tex != (Texture *)NULL) {
|
|
sampler = tex->get_default_sampler();
|
|
}
|
|
return tex;
|
|
|
|
} else {
|
|
if (spec._stage == 0) {
|
|
display_cat.error()
|
|
<< "Shader input " << *parent
|
|
<< " has no member named " << basename << ".\n";
|
|
spec._stage = -1;
|
|
}
|
|
}
|
|
} else {
|
|
// This used to be legal for some reason, so don't trigger the assert.
|
|
// Prevent flood, though, so abuse the _stage flag to indicate whether
|
|
// we've already errored about this.
|
|
if (spec._stage == 0) {
|
|
display_cat.error()
|
|
<< "Shader input " << *spec._name << " is not present.\n";
|
|
spec._stage = -1;
|
|
}
|
|
}
|
|
} else {
|
|
// Just a regular texture input.
|
|
return _target_shader->get_shader_input_texture(spec._name, &sampler);
|
|
}
|
|
break;
|
|
|
|
case Shader::STO_stage_i:
|
|
{
|
|
// We get the TextureAttrib directly from the _target_rs, not the
|
|
// filtered TextureAttrib in _target_texture.
|
|
const TextureAttrib *texattrib;
|
|
_target_rs->get_attrib_def(texattrib);
|
|
|
|
if (spec._stage < texattrib->get_num_on_stages()) {
|
|
TextureStage *stage = texattrib->get_on_stage(spec._stage);
|
|
sampler = texattrib->get_on_sampler(stage);
|
|
view += stage->get_tex_view_offset();
|
|
return texattrib->get_on_texture(stage);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Shader::STO_light_i_shadow_map:
|
|
{
|
|
const LightAttrib *target_light;
|
|
_target_rs->get_attrib_def(target_light);
|
|
|
|
// We want to ignore ambient lights. To that effect, iterate through
|
|
// the list of lights. In the future, we will improve this system, by
|
|
// also filtering down to the number of lights specified by the shader.
|
|
int i = 0;
|
|
|
|
int num_on_lights = target_light->get_num_on_lights();
|
|
for (int li = 0; li < num_on_lights; li++) {
|
|
NodePath light = target_light->get_on_light(li);
|
|
nassertr(!light.is_empty(), NULL);
|
|
Light *light_obj = light.node()->as_light();
|
|
nassertr(light_obj != (Light *)NULL, NULL);
|
|
|
|
if (light_obj->get_type() != AmbientLight::get_class_type()) {
|
|
if (i++ == spec._stage) {
|
|
PT(Texture) tex = get_shadow_map(light);
|
|
if (tex != (Texture *)NULL) {
|
|
sampler = tex->get_default_sampler();
|
|
}
|
|
return tex;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
nassertr(false, NULL);
|
|
break;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Return a pointer to struct ShaderPtrData
|
|
*/
|
|
const Shader::ShaderPtrData *GraphicsStateGuardian::
|
|
fetch_ptr_parameter(const Shader::ShaderPtrSpec& spec) {
|
|
return (_target_shader->get_shader_input_ptr(spec._arg));
|
|
}
|
|
|
|
/**
|
|
* Makes the specified DisplayRegion current. All future drawing and clear
|
|
* operations will be constrained within the given DisplayRegion.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
prepare_display_region(DisplayRegionPipelineReader *dr) {
|
|
_current_display_region = dr->get_object();
|
|
_current_stereo_channel = dr->get_stereo_channel();
|
|
_current_tex_view_offset = dr->get_tex_view_offset();
|
|
_effective_incomplete_render = _incomplete_render && _current_display_region->get_incomplete_render();
|
|
|
|
_stereo_buffer_mask = ~0;
|
|
|
|
Lens::StereoChannel output_channel = dr->get_stereo_channel();
|
|
if (dr->get_window()->get_swap_eyes()) {
|
|
// Reverse the output channel.
|
|
switch (output_channel) {
|
|
case Lens::SC_left:
|
|
output_channel = Lens::SC_right;
|
|
break;
|
|
|
|
case Lens::SC_right:
|
|
output_channel = Lens::SC_left;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch (output_channel) {
|
|
case Lens::SC_left:
|
|
_color_write_mask = dr->get_window()->get_left_eye_color_mask();
|
|
if (_current_properties->is_stereo()) {
|
|
_stereo_buffer_mask = ~RenderBuffer::T_right;
|
|
}
|
|
break;
|
|
|
|
case Lens::SC_right:
|
|
_color_write_mask = dr->get_window()->get_right_eye_color_mask();
|
|
if (_current_properties->is_stereo()) {
|
|
_stereo_buffer_mask = ~RenderBuffer::T_left;
|
|
}
|
|
break;
|
|
|
|
case Lens::SC_mono:
|
|
case Lens::SC_stereo:
|
|
_color_write_mask = ColorWriteAttrib::C_all;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resets any non-standard graphics state that might give a callback apoplexy.
|
|
* Some drivers require that the graphics state be restored to neutral before
|
|
* performing certain operations. In OpenGL, for instance, this closes any
|
|
* open vertex buffers.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
clear_before_callback() {
|
|
}
|
|
|
|
/**
|
|
* Forgets the current graphics state and current transform, so that the next
|
|
* call to set_state_and_transform() will have to reload everything. This is
|
|
* a good thing to call when you are no longer sure what the graphics state
|
|
* is. This should only be called from the draw thread.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
clear_state_and_transform() {
|
|
// Re-issue the modelview and projection transforms.
|
|
reissue_transforms();
|
|
|
|
// Now clear the state flags to unknown.
|
|
_state_rs = RenderState::make_empty();
|
|
_state_mask.clear();
|
|
}
|
|
|
|
/**
|
|
* This is simply a transparent call to GraphicsEngine::remove_window(). It
|
|
* exists primary to support removing a window from that compiles before the
|
|
* display module, and therefore has no knowledge of a GraphicsEngine object.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
remove_window(GraphicsOutputBase *window) {
|
|
nassertv(_engine != (GraphicsEngine *)NULL);
|
|
GraphicsOutput *win;
|
|
DCAST_INTO_V(win, window);
|
|
_engine->remove_window(win);
|
|
}
|
|
|
|
/**
|
|
* Makes the current lens (whichever lens was most recently specified with
|
|
* set_scene()) active, so that it will transform future rendered geometry.
|
|
* Normally this is only called from the draw process, and usually it is
|
|
* called by set_scene().
|
|
*
|
|
* The return value is true if the lens is acceptable, false if it is not.
|
|
*/
|
|
bool GraphicsStateGuardian::
|
|
prepare_lens() {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Given a lens, this function calculates the appropriate projection matrix
|
|
* for this gsg. The result depends on the peculiarities of the rendering
|
|
* API.
|
|
*/
|
|
CPT(TransformState) GraphicsStateGuardian::
|
|
calc_projection_mat(const Lens *lens) {
|
|
if (lens == (Lens *)NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
if (!lens->is_linear()) {
|
|
return NULL;
|
|
}
|
|
|
|
return TransformState::make_identity();
|
|
}
|
|
|
|
/**
|
|
* Called before each frame is rendered, to allow the GSG a chance to do any
|
|
* internal cleanup before beginning the frame.
|
|
*
|
|
* The return value is true if successful (in which case the frame will be
|
|
* drawn and end_frame() will be called later), or false if unsuccessful (in
|
|
* which case nothing will be drawn and end_frame() will not be called).
|
|
*/
|
|
bool GraphicsStateGuardian::
|
|
begin_frame(Thread *current_thread) {
|
|
_prepared_objects->begin_frame(this, current_thread);
|
|
|
|
// We should reset the state to the default at the beginning of every frame.
|
|
// Although this will incur additional overhead, particularly in a simple
|
|
// scene, it helps ensure that states that have changed properties since
|
|
// last time without changing attribute pointers--like textures, lighting,
|
|
// or fog--will still be accurately updated.
|
|
_state_rs = RenderState::make_empty();
|
|
_state_mask.clear();
|
|
|
|
#ifdef DO_PSTATS
|
|
// We have to do this here instead of in GraphicsEngine because we need a
|
|
// current context to issue timer queries.
|
|
int frame = ClockObject::get_global_clock()->get_frame_count();
|
|
if (_last_query_frame < frame) {
|
|
_last_query_frame = frame;
|
|
_timer_queries_pcollector.clear_level();
|
|
|
|
// Now is a good time to flush previous frame's queries. We may not
|
|
// actually have all of the previous frame's results in yet, but that's
|
|
// okay; the GPU data is allowed to lag a few frames behind.
|
|
flush_timer_queries();
|
|
|
|
if (_timer_queries_active) {
|
|
// Issue a stop and start event for collector 0, marking the beginning
|
|
// of the new frame.
|
|
issue_timer_query(0x8000);
|
|
issue_timer_query(0x0000);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return !_needs_reset;
|
|
}
|
|
|
|
/**
|
|
* Called between begin_frame() and end_frame() to mark the beginning of
|
|
* drawing commands for a "scene" (usually a particular DisplayRegion) within
|
|
* a frame. All 3-D drawing commands, except the clear operation, must be
|
|
* enclosed within begin_scene() .. end_scene(). This must be called in the
|
|
* draw thread.
|
|
*
|
|
* The return value is true if successful (in which case the scene will be
|
|
* drawn and end_scene() will be called later), or false if unsuccessful (in
|
|
* which case nothing will be drawn and end_scene() will not be called).
|
|
*/
|
|
bool GraphicsStateGuardian::
|
|
begin_scene() {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Called between begin_frame() and end_frame() to mark the end of drawing
|
|
* commands for a "scene" (usually a particular DisplayRegion) within a frame.
|
|
* All 3-D drawing commands, except the clear operation, must be enclosed
|
|
* within begin_scene() .. end_scene().
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
end_scene() {
|
|
// We should clear this pointer now, so that we don't keep unneeded
|
|
// reference counts dangling. We keep around a "null" scene setup object
|
|
// instead of using a null pointer to avoid special-case code in
|
|
// set_state_and_transform.
|
|
_scene_setup = _scene_null;
|
|
|
|
// Undo any lighting we had enabled last scene, to force the lights to be
|
|
// reissued, in case their parameters or positions have changed between
|
|
// scenes.
|
|
int i;
|
|
for (i = 0; i < _num_lights_enabled; ++i) {
|
|
enable_light(i, false);
|
|
}
|
|
_num_lights_enabled = 0;
|
|
|
|
// Ditto for the clipping planes.
|
|
for (i = 0; i < _num_clip_planes_enabled; ++i) {
|
|
enable_clip_plane(i, false);
|
|
}
|
|
_num_clip_planes_enabled = 0;
|
|
|
|
// Put the state into the 'unknown' state, forcing a reload.
|
|
_state_rs = RenderState::make_empty();
|
|
_state_mask.clear();
|
|
}
|
|
|
|
/**
|
|
* Called after each frame is rendered, to allow the GSG a chance to do any
|
|
* internal cleanup after rendering the frame, and before the window flips.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
end_frame(Thread *current_thread) {
|
|
_prepared_objects->end_frame(current_thread);
|
|
|
|
// Flush any PStatCollectors.
|
|
_data_transferred_pcollector.flush_level();
|
|
|
|
_primitive_batches_pcollector.flush_level();
|
|
_primitive_batches_tristrip_pcollector.flush_level();
|
|
_primitive_batches_trifan_pcollector.flush_level();
|
|
_primitive_batches_tri_pcollector.flush_level();
|
|
_primitive_batches_patch_pcollector.flush_level();
|
|
_primitive_batches_other_pcollector.flush_level();
|
|
_vertices_tristrip_pcollector.flush_level();
|
|
_vertices_trifan_pcollector.flush_level();
|
|
_vertices_tri_pcollector.flush_level();
|
|
_vertices_patch_pcollector.flush_level();
|
|
_vertices_other_pcollector.flush_level();
|
|
|
|
_state_pcollector.flush_level();
|
|
_texture_state_pcollector.flush_level();
|
|
_transform_state_pcollector.flush_level();
|
|
_draw_primitive_pcollector.flush_level();
|
|
|
|
// Evict any textures andor vbuffers that exceed our texture memory.
|
|
_prepared_objects->_graphics_memory_lru.begin_epoch();
|
|
}
|
|
|
|
/**
|
|
* Called by the graphics engine on the draw thread to check the status of the
|
|
* running timer queries and submit their results to the PStats server.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
flush_timer_queries() {
|
|
#ifdef DO_PSTATS
|
|
// This uses the lower-level PStats interfaces for now because of all the
|
|
// unnecessary overhead that would otherwise be incurred when adding such a
|
|
// large amount of data at once.
|
|
|
|
PStatClient *client = PStatClient::get_global_pstats();
|
|
|
|
if (!client->client_is_connected()) {
|
|
_timer_queries_active = false;
|
|
return;
|
|
}
|
|
|
|
if (!_timer_queries_active) {
|
|
if (pstats_gpu_timing && _supports_timer_query) {
|
|
// Check if timer queries should be enabled.
|
|
_timer_queries_active = true;
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Currently, we use one thread per GSG, for convenience. In the future, we
|
|
// may want to try and use one thread per graphics card.
|
|
if (_pstats_gpu_thread == -1) {
|
|
_pstats_gpu_thread = client->make_gpu_thread(get_driver_renderer()).get_index();
|
|
}
|
|
PStatThread gpu_thread(client, _pstats_gpu_thread);
|
|
|
|
// Get the results of all the timer queries.
|
|
int first = 0;
|
|
if (!_pending_timer_queries.empty()) {
|
|
int count = _pending_timer_queries.size();
|
|
if (count == 0) {
|
|
return;
|
|
}
|
|
|
|
PStatGPUTimer timer(this, _wait_timer_pcollector);
|
|
|
|
if (_last_num_queried > 0) {
|
|
// We know how many queries were available last frame, and this usually
|
|
// stays fairly constant, so use this as a starting point.
|
|
int i = min(_last_num_queried, count) - 1;
|
|
|
|
if (_pending_timer_queries[i]->is_answer_ready()) {
|
|
first = count;
|
|
while (i < count - 1) {
|
|
if (!_pending_timer_queries[++i]->is_answer_ready()) {
|
|
first = i;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
first = 0;
|
|
while (i > 0) {
|
|
if (_pending_timer_queries[--i]->is_answer_ready()) {
|
|
first = i + 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// We figure out which tasks the GPU has already finished by doing a
|
|
// binary search for the first query that does not have an answer ready.
|
|
// We know then that everything before that must be ready.
|
|
while (count > 0) {
|
|
int step = count / 2;
|
|
int i = first + step;
|
|
if (_pending_timer_queries[i]->is_answer_ready()) {
|
|
first += step + 1;
|
|
count -= step + 1;
|
|
} else {
|
|
count = step;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (first <= 0) {
|
|
return;
|
|
}
|
|
|
|
_last_num_queried = first;
|
|
|
|
for (int i = 0; i < first; ++i) {
|
|
CPT(TimerQueryContext) query = _pending_timer_queries[i];
|
|
|
|
double time_data = query->get_timestamp(); // + _timer_delta;
|
|
|
|
if (query->_pstats_index == _command_latency_pcollector.get_index()) {
|
|
// Special case for the latency pcollector.
|
|
PStatCollectorDef *cdef;
|
|
cdef = client->get_collector_ptr(query->_pstats_index)->get_def(client, query->_pstats_index);
|
|
_pstats_gpu_data.add_level(query->_pstats_index, time_data * cdef->_factor);
|
|
|
|
} else if (query->_pstats_index & 0x8000) {
|
|
_pstats_gpu_data.add_stop(query->_pstats_index & 0x7fff, time_data);
|
|
|
|
} else {
|
|
_pstats_gpu_data.add_start(query->_pstats_index & 0x7fff, time_data);
|
|
}
|
|
|
|
// We found an end-frame marker (a stop event for collector 0). This
|
|
// means that the GPU actually caught up with that frame, and we can
|
|
// flush the GPU thread's frame data to the pstats server.
|
|
if (query->_pstats_index == 0x8000) {
|
|
gpu_thread.add_frame(_pstats_gpu_data);
|
|
_pstats_gpu_data.clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (first > 0) {
|
|
// Do this out of the scope of _wait_timer_pcollector.
|
|
_pending_timer_queries.erase(
|
|
_pending_timer_queries.begin(),
|
|
_pending_timer_queries.begin() + first
|
|
);
|
|
_timer_queries_pcollector.add_level_now(first);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Returns true if this GSG can implement decals using a DepthOffsetAttrib, or
|
|
* false if that is unreliable and the three-step rendering process should be
|
|
* used instead.
|
|
*/
|
|
bool GraphicsStateGuardian::
|
|
depth_offset_decals() {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Called during draw to begin a three-step rendering phase to draw decals.
|
|
* The first step, begin_decal_base_first(), is called prior to drawing the
|
|
* base geometry. It should set up whatever internal state is appropriate, as
|
|
* well as returning a RenderState object that should be applied to the base
|
|
* geometry for rendering.
|
|
*/
|
|
CPT(RenderState) GraphicsStateGuardian::
|
|
begin_decal_base_first() {
|
|
// Turn off writing the depth buffer to render the base geometry.
|
|
static CPT(RenderState) decal_base_first;
|
|
if (decal_base_first == (const RenderState *)NULL) {
|
|
decal_base_first = RenderState::make
|
|
(DepthWriteAttrib::make(DepthWriteAttrib::M_off),
|
|
RenderState::get_max_priority());
|
|
}
|
|
return decal_base_first;
|
|
}
|
|
|
|
/**
|
|
* Called during draw to begin a three-step rendering phase to draw decals.
|
|
* The second step, begin_decal_nested(), is called after drawing the base
|
|
* geometry and prior to drawing any of the nested decal geometry that is to
|
|
* be applied to the base geometry.
|
|
*/
|
|
CPT(RenderState) GraphicsStateGuardian::
|
|
begin_decal_nested() {
|
|
// We should keep the depth buffer off during this operation, so that decals
|
|
// on decals will render properly.
|
|
static CPT(RenderState) decal_nested;
|
|
if (decal_nested == (const RenderState *)NULL) {
|
|
decal_nested = RenderState::make
|
|
(DepthWriteAttrib::make(DepthWriteAttrib::M_off),
|
|
RenderState::get_max_priority());
|
|
}
|
|
return decal_nested;
|
|
}
|
|
|
|
/**
|
|
* Called during draw to begin a three-step rendering phase to draw decals.
|
|
* The third step, begin_decal_base_second(), is called after drawing the base
|
|
* geometry and the nested decal geometry, and prior to drawing the base
|
|
* geometry one more time (if needed).
|
|
*
|
|
* It should return a RenderState object appropriate for rendering the base
|
|
* geometry the second time, or NULL if it is not necessary to re-render the
|
|
* base geometry.
|
|
*/
|
|
CPT(RenderState) GraphicsStateGuardian::
|
|
begin_decal_base_second() {
|
|
// Now let the depth buffer go back on, but turn off writing the color
|
|
// buffer to render the base geometry after the second pass. Also, turn off
|
|
// texturing since there's no need for it now.
|
|
static CPT(RenderState) decal_base_second;
|
|
if (decal_base_second == (const RenderState *)NULL) {
|
|
decal_base_second = RenderState::make
|
|
(ColorWriteAttrib::make(ColorWriteAttrib::C_off),
|
|
// On reflection, we need to leave texturing on so the alpha test
|
|
// mechanism can work (if it is enabled, e.g. we are rendering an
|
|
// object with M_dual transparency). TextureAttrib::make_off(),
|
|
RenderState::get_max_priority());
|
|
}
|
|
return decal_base_second;
|
|
}
|
|
|
|
/**
|
|
* Called during draw to clean up after decals are finished.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
finish_decal() {
|
|
// No need to do anything special here.
|
|
}
|
|
|
|
/**
|
|
* Called before a sequence of draw_primitive() functions are called, this
|
|
* should prepare the vertex data for rendering. It returns true if the
|
|
* vertices are ok, false to abort this group of primitives.
|
|
*/
|
|
bool GraphicsStateGuardian::
|
|
begin_draw_primitives(const GeomPipelineReader *geom_reader,
|
|
const GeomMunger *munger,
|
|
const GeomVertexDataPipelineReader *data_reader,
|
|
bool force) {
|
|
_munger = munger;
|
|
_data_reader = data_reader;
|
|
|
|
// Always draw if we have a shader, since the shader might use a different
|
|
// mechanism for fetching vertex data.
|
|
return _data_reader->has_vertex() || (_target_shader && _target_shader->has_shader());
|
|
}
|
|
|
|
/**
|
|
* Draws a series of disconnected triangles.
|
|
*/
|
|
bool GraphicsStateGuardian::
|
|
draw_triangles(const GeomPrimitivePipelineReader *, bool) {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Draws a series of triangle strips.
|
|
*/
|
|
bool GraphicsStateGuardian::
|
|
draw_tristrips(const GeomPrimitivePipelineReader *, bool) {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Draws a series of triangle fans.
|
|
*/
|
|
bool GraphicsStateGuardian::
|
|
draw_trifans(const GeomPrimitivePipelineReader *, bool) {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Draws a series of "patches", which can only be processed by a tessellation
|
|
* shader.
|
|
*/
|
|
bool GraphicsStateGuardian::
|
|
draw_patches(const GeomPrimitivePipelineReader *, bool) {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Draws a series of disconnected line segments.
|
|
*/
|
|
bool GraphicsStateGuardian::
|
|
draw_lines(const GeomPrimitivePipelineReader *, bool) {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Draws a series of line strips.
|
|
*/
|
|
bool GraphicsStateGuardian::
|
|
draw_linestrips(const GeomPrimitivePipelineReader *, bool) {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Draws a series of disconnected points.
|
|
*/
|
|
bool GraphicsStateGuardian::
|
|
draw_points(const GeomPrimitivePipelineReader *, bool) {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Called after a sequence of draw_primitive() functions are called, this
|
|
* should do whatever cleanup is appropriate.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
end_draw_primitives() {
|
|
_munger = NULL;
|
|
_data_reader = NULL;
|
|
}
|
|
|
|
/**
|
|
* Resets all internal state as if the gsg were newly created.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
reset() {
|
|
_needs_reset = false;
|
|
_is_valid = false;
|
|
|
|
_state_rs = RenderState::make_empty();
|
|
_target_rs = NULL;
|
|
_state_mask.clear();
|
|
_internal_transform = _cs_transform;
|
|
_scene_null = new SceneSetup;
|
|
_scene_setup = _scene_null;
|
|
|
|
_color_write_mask = ColorWriteAttrib::C_all;
|
|
|
|
_has_scene_graph_color = false;
|
|
_scene_graph_color.set(1.0f, 1.0f, 1.0f, 1.0f);
|
|
_transform_stale = true;
|
|
_color_blend_involves_color_scale = false;
|
|
_texture_involves_color_scale = false;
|
|
_vertex_colors_enabled = true;
|
|
_lighting_enabled = false;
|
|
_num_lights_enabled = 0;
|
|
_num_clip_planes_enabled = 0;
|
|
_clip_planes_enabled = false;
|
|
|
|
_color_scale_enabled = false;
|
|
_current_color_scale.set(1.0f, 1.0f, 1.0f, 1.0f);
|
|
_has_texture_alpha_scale = false;
|
|
|
|
_has_material_force_color = false;
|
|
_material_force_color.set(1.0f, 1.0f, 1.0f, 1.0f);
|
|
_light_color_scale.set(1.0f, 1.0f, 1.0f, 1.0f);
|
|
|
|
_tex_gen_modifies_mat = false;
|
|
_last_max_stage_index = 0;
|
|
|
|
_is_valid = true;
|
|
}
|
|
|
|
/**
|
|
* Simultaneously resets the render state and the transform state.
|
|
*
|
|
* This transform specified is the "internal" net transform, already converted
|
|
* into the GSG's internal coordinate space by composing it to
|
|
* get_cs_transform(). (Previously, this used to be the "external" net
|
|
* transform, with the assumption that that GSG would convert it internally,
|
|
* but that is no longer the case.)
|
|
*
|
|
* Special case: if (state==NULL), then the target state is already stored in
|
|
* _target.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
set_state_and_transform(const RenderState *state,
|
|
const TransformState *trans) {
|
|
}
|
|
|
|
/**
|
|
* Clears the framebuffer within the current DisplayRegion, according to the
|
|
* flags indicated by the given DrawableRegion object.
|
|
*
|
|
* This does not set the DisplayRegion first. You should call
|
|
* prepare_display_region() to specify the region you wish the clear operation
|
|
* to apply to.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
clear(DrawableRegion *clearable) {
|
|
}
|
|
|
|
/**
|
|
* Returns a RenderBuffer object suitable for operating on the requested set
|
|
* of buffers. buffer_type is the union of all the desired RenderBuffer::Type
|
|
* values.
|
|
*/
|
|
RenderBuffer GraphicsStateGuardian::
|
|
get_render_buffer(int buffer_type, const FrameBufferProperties &prop) {
|
|
return RenderBuffer(this, buffer_type & prop.get_buffer_mask() & _stereo_buffer_mask);
|
|
}
|
|
|
|
/**
|
|
* Returns what the cs_transform would be set to after a call to
|
|
* set_coordinate_system(cs). This is another way of saying the cs_transform
|
|
* when rendering the scene for a camera with the indicated coordinate system.
|
|
*/
|
|
CPT(TransformState) GraphicsStateGuardian::
|
|
get_cs_transform_for(CoordinateSystem cs) const {
|
|
if (_coordinate_system == cs) {
|
|
// We've already calculated this.
|
|
return _cs_transform;
|
|
|
|
} else if (_internal_coordinate_system == CS_default ||
|
|
_internal_coordinate_system == cs) {
|
|
return TransformState::make_identity();
|
|
|
|
} else {
|
|
return TransformState::make_mat
|
|
(LMatrix4::convert_mat(cs, _internal_coordinate_system));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a transform that converts from the GSG's external coordinate system
|
|
* (as returned by get_coordinate_system()) to its internal coordinate system
|
|
* (as returned by get_internal_coordinate_system()). This is used for
|
|
* rendering.
|
|
*/
|
|
CPT(TransformState) GraphicsStateGuardian::
|
|
get_cs_transform() const {
|
|
return _cs_transform;
|
|
}
|
|
|
|
/**
|
|
* This is fundametically similar to do_issue_light(), with calls to
|
|
* apply_clip_plane() and enable_clip_planes(), as appropriate.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
do_issue_clip_plane() {
|
|
int num_enabled = 0;
|
|
int num_on_planes = 0;
|
|
|
|
const ClipPlaneAttrib *target_clip_plane = (const ClipPlaneAttrib *)
|
|
_target_rs->get_attrib_def(ClipPlaneAttrib::get_class_slot());
|
|
|
|
if (target_clip_plane != (ClipPlaneAttrib *)NULL) {
|
|
CPT(ClipPlaneAttrib) new_plane = target_clip_plane->filter_to_max(_max_clip_planes);
|
|
|
|
num_on_planes = new_plane->get_num_on_planes();
|
|
for (int li = 0; li < num_on_planes; li++) {
|
|
NodePath plane = new_plane->get_on_plane(li);
|
|
nassertv(!plane.is_empty());
|
|
PlaneNode *plane_node;
|
|
DCAST_INTO_V(plane_node, plane.node());
|
|
if ((plane_node->get_clip_effect() & PlaneNode::CE_visible) != 0) {
|
|
// Clipping should be enabled before we apply any planes.
|
|
if (!_clip_planes_enabled) {
|
|
enable_clip_planes(true);
|
|
_clip_planes_enabled = true;
|
|
}
|
|
|
|
enable_clip_plane(num_enabled, true);
|
|
if (num_enabled == 0) {
|
|
begin_bind_clip_planes();
|
|
}
|
|
|
|
bind_clip_plane(plane, num_enabled);
|
|
num_enabled++;
|
|
}
|
|
}
|
|
}
|
|
|
|
int i;
|
|
for (i = num_enabled; i < _num_clip_planes_enabled; ++i) {
|
|
enable_clip_plane(i, false);
|
|
}
|
|
_num_clip_planes_enabled = num_enabled;
|
|
|
|
// If no planes were set, disable clipping
|
|
if (num_enabled == 0) {
|
|
if (_clip_planes_enabled) {
|
|
enable_clip_planes(false);
|
|
_clip_planes_enabled = false;
|
|
}
|
|
} else {
|
|
end_bind_clip_planes();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This method is defined in the base class because it is likely that this
|
|
* functionality will be used for all (or at least most) kinds of
|
|
* GraphicsStateGuardians--it's not specific to any one rendering backend.
|
|
*
|
|
* The ColorAttribute just changes the interpretation of the color on the
|
|
* vertices, and fiddles with _vertex_colors_enabled, etc.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
do_issue_color() {
|
|
const ColorAttrib *target_color = (const ColorAttrib *)
|
|
_target_rs->get_attrib_def(ColorAttrib::get_class_slot());
|
|
|
|
switch (target_color->get_color_type()) {
|
|
case ColorAttrib::T_flat:
|
|
// Color attribute flat: it specifies a scene graph color that overrides
|
|
// the vertex color.
|
|
_scene_graph_color = target_color->get_color();
|
|
_has_scene_graph_color = true;
|
|
_vertex_colors_enabled = false;
|
|
break;
|
|
|
|
case ColorAttrib::T_off:
|
|
// Color attribute off: it specifies that no scene graph color is in
|
|
// effect, and vertex color is not important either.
|
|
_scene_graph_color.set(1.0f, 1.0f, 1.0f, 1.0f);
|
|
_has_scene_graph_color = false;
|
|
_vertex_colors_enabled = false;
|
|
break;
|
|
|
|
case ColorAttrib::T_vertex:
|
|
// Color attribute vertex: it specifies that vertex color should be
|
|
// revealed.
|
|
_scene_graph_color.set(1.0f, 1.0f, 1.0f, 1.0f);
|
|
_has_scene_graph_color = false;
|
|
_vertex_colors_enabled = true;
|
|
break;
|
|
}
|
|
|
|
if (_color_scale_via_lighting) {
|
|
_state_mask.clear_bit(LightAttrib::get_class_slot());
|
|
_state_mask.clear_bit(MaterialAttrib::get_class_slot());
|
|
|
|
determine_light_color_scale();
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
do_issue_color_scale() {
|
|
// If the previous color scale had set a special texture, clear the texture
|
|
// now.
|
|
if (_has_texture_alpha_scale) {
|
|
_state_mask.clear_bit(TextureAttrib::get_class_slot());
|
|
}
|
|
|
|
const ColorScaleAttrib *target_color_scale = (const ColorScaleAttrib *)
|
|
_target_rs->get_attrib_def(ColorScaleAttrib::get_class_slot());
|
|
|
|
_color_scale_enabled = target_color_scale->has_scale();
|
|
_current_color_scale = target_color_scale->get_scale();
|
|
_has_texture_alpha_scale = false;
|
|
|
|
if (_color_blend_involves_color_scale) {
|
|
_state_mask.clear_bit(TransparencyAttrib::get_class_slot());
|
|
}
|
|
if (_texture_involves_color_scale) {
|
|
_state_mask.clear_bit(TextureAttrib::get_class_slot());
|
|
}
|
|
if (_color_scale_via_lighting) {
|
|
_state_mask.clear_bit(LightAttrib::get_class_slot());
|
|
_state_mask.clear_bit(MaterialAttrib::get_class_slot());
|
|
|
|
determine_light_color_scale();
|
|
}
|
|
|
|
if (_alpha_scale_via_texture && !_has_scene_graph_color &&
|
|
target_color_scale->has_alpha_scale()) {
|
|
// This color scale will set a special texture--so again, clear the
|
|
// texture.
|
|
_state_mask.clear_bit(TextureAttrib::get_class_slot());
|
|
_state_mask.clear_bit(TexMatrixAttrib::get_class_slot());
|
|
|
|
_has_texture_alpha_scale = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This implementation of do_issue_light() assumes we have a limited number of
|
|
* hardware lights available. This function assigns each light to a different
|
|
* hardware light id, trying to keep each light associated with the same id
|
|
* where possible, but reusing id's when necessary. When it is no longer
|
|
* possible to reuse existing id's (e.g. all id's are in use), the next
|
|
* sequential id is assigned (if available).
|
|
*
|
|
* It will call apply_light() each time a light is assigned to a particular id
|
|
* for the first time in a given frame, and it will subsequently call
|
|
* enable_light() to enable or disable each light as the frame is rendered, as
|
|
* well as enable_lighting() to enable or disable overall lighting.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
do_issue_light() {
|
|
// Initialize the current ambient light total and newly enabled light list
|
|
LColor cur_ambient_light(0.0f, 0.0f, 0.0f, 0.0f);
|
|
int i;
|
|
|
|
int num_enabled = 0;
|
|
int num_on_lights = 0;
|
|
|
|
const LightAttrib *target_light;
|
|
_target_rs->get_attrib_def(target_light);
|
|
|
|
if (display_cat.is_spam()) {
|
|
display_cat.spam()
|
|
<< "do_issue_light: " << target_light << "\n";
|
|
}
|
|
if (target_light != (LightAttrib *)NULL) {
|
|
CPT(LightAttrib) new_light = target_light->filter_to_max(_max_lights);
|
|
if (display_cat.is_spam()) {
|
|
new_light->write(display_cat.spam(false), 2);
|
|
}
|
|
|
|
num_on_lights = new_light->get_num_on_lights();
|
|
for (int li = 0; li < num_on_lights; li++) {
|
|
NodePath light = new_light->get_on_light(li);
|
|
nassertv(!light.is_empty());
|
|
Light *light_obj = light.node()->as_light();
|
|
nassertv(light_obj != (Light *)NULL);
|
|
|
|
// Lighting should be enabled before we apply any lights.
|
|
if (!_lighting_enabled) {
|
|
enable_lighting(true);
|
|
_lighting_enabled = true;
|
|
}
|
|
|
|
if (light_obj->get_type() == AmbientLight::get_class_type()) {
|
|
// Ambient lights don't require specific light ids; simply add in the
|
|
// ambient contribution to the current total
|
|
cur_ambient_light += light_obj->get_color();
|
|
|
|
} else {
|
|
const LColor &color = light_obj->get_color();
|
|
// Don't bother binding the light if it has no color to contribute.
|
|
if (color[0] != 0.0 || color[1] != 0.0 || color[2] != 0.0) {
|
|
enable_light(num_enabled, true);
|
|
if (num_enabled == 0) {
|
|
begin_bind_lights();
|
|
}
|
|
|
|
light_obj->bind(this, light, num_enabled);
|
|
num_enabled++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = num_enabled; i < _num_lights_enabled; ++i) {
|
|
enable_light(i, false);
|
|
}
|
|
_num_lights_enabled = num_enabled;
|
|
|
|
// If no lights were set, disable lighting
|
|
if (num_on_lights == 0) {
|
|
if (_color_scale_via_lighting && (_has_material_force_color || _light_color_scale != LVecBase4(1.0f, 1.0f, 1.0f, 1.0f))) {
|
|
// Unless we need lighting anyway to apply a color or color scale.
|
|
if (!_lighting_enabled) {
|
|
enable_lighting(true);
|
|
_lighting_enabled = true;
|
|
}
|
|
set_ambient_light(LColor(1.0f, 1.0f, 1.0f, 1.0f));
|
|
|
|
} else {
|
|
if (_lighting_enabled) {
|
|
enable_lighting(false);
|
|
_lighting_enabled = false;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
set_ambient_light(cur_ambient_light);
|
|
}
|
|
|
|
if (num_enabled != 0) {
|
|
end_bind_lights();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Copy the pixels within the indicated display region from the framebuffer
|
|
* into texture memory.
|
|
*
|
|
* If z > -1, it is the cube map index into which to copy.
|
|
*/
|
|
bool GraphicsStateGuardian::
|
|
framebuffer_copy_to_texture(Texture *, int, int, const DisplayRegion *,
|
|
const RenderBuffer &) {
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Copy the pixels within the indicated display region from the framebuffer
|
|
* into system memory, not texture memory. Returns true on success, false on
|
|
* failure.
|
|
*
|
|
* This completely redefines the ram image of the indicated texture.
|
|
*/
|
|
bool GraphicsStateGuardian::
|
|
framebuffer_copy_to_ram(Texture *, int, int, const DisplayRegion *,
|
|
const RenderBuffer &) {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Called the first time a particular light has been bound to a given id
|
|
* within a frame, this should set up the associated hardware light with the
|
|
* light's properties.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
bind_light(PointLight *light_obj, const NodePath &light, int light_id) {
|
|
}
|
|
|
|
/**
|
|
* Called the first time a particular light has been bound to a given id
|
|
* within a frame, this should set up the associated hardware light with the
|
|
* light's properties.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
bind_light(DirectionalLight *light_obj, const NodePath &light, int light_id) {
|
|
}
|
|
|
|
/**
|
|
* Called the first time a particular light has been bound to a given id
|
|
* within a frame, this should set up the associated hardware light with the
|
|
* light's properties.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
bind_light(Spotlight *light_obj, const NodePath &light, int light_id) {
|
|
}
|
|
|
|
#ifdef DO_PSTATS
|
|
/**
|
|
* Initializes the relevant PStats data at the beginning of the frame.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
init_frame_pstats() {
|
|
if (PStatClient::is_connected()) {
|
|
_data_transferred_pcollector.clear_level();
|
|
_vertex_buffer_switch_pcollector.clear_level();
|
|
_index_buffer_switch_pcollector.clear_level();
|
|
|
|
_primitive_batches_pcollector.clear_level();
|
|
_primitive_batches_tristrip_pcollector.clear_level();
|
|
_primitive_batches_trifan_pcollector.clear_level();
|
|
_primitive_batches_tri_pcollector.clear_level();
|
|
_primitive_batches_patch_pcollector.clear_level();
|
|
_primitive_batches_other_pcollector.clear_level();
|
|
_vertices_tristrip_pcollector.clear_level();
|
|
_vertices_trifan_pcollector.clear_level();
|
|
_vertices_tri_pcollector.clear_level();
|
|
_vertices_patch_pcollector.clear_level();
|
|
_vertices_other_pcollector.clear_level();
|
|
|
|
_state_pcollector.clear_level();
|
|
_transform_state_pcollector.clear_level();
|
|
_texture_state_pcollector.clear_level();
|
|
}
|
|
}
|
|
#endif // DO_PSTATS
|
|
|
|
|
|
/**
|
|
* Create a gamma table.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
create_gamma_table (PN_stdfloat gamma, unsigned short *red_table, unsigned short *green_table, unsigned short *blue_table) {
|
|
int i;
|
|
|
|
if (gamma <= 0.0) {
|
|
// avoid divide by zero and negative exponents
|
|
gamma = 1.0;
|
|
}
|
|
|
|
for (i = 0; i < 256; i++) {
|
|
double g;
|
|
double x;
|
|
PN_stdfloat gamma_correction;
|
|
|
|
x = ((double) i / 255.0);
|
|
gamma_correction = 1.0 / gamma;
|
|
x = pow (x, (double) gamma_correction);
|
|
if (x > 1.00) {
|
|
x = 1.0;
|
|
}
|
|
|
|
g = x * 65535.0;
|
|
red_table [i] = (int)g;
|
|
green_table [i] = (int)g;
|
|
blue_table [i] = (int)g;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called by clear_state_and_transform() to ensure that the current modelview
|
|
* and projection matrices are properly loaded in the graphics state, after a
|
|
* callback might have mucked them up.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
reissue_transforms() {
|
|
}
|
|
|
|
/**
|
|
* Intended to be overridden by a derived class to enable or disable the use
|
|
* of lighting overall. This is called by do_issue_light() according to
|
|
* whether any lights are in use or not.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
enable_lighting(bool enable) {
|
|
}
|
|
|
|
/**
|
|
* Intended to be overridden by a derived class to indicate the color of the
|
|
* ambient light that should be in effect. This is called by do_issue_light()
|
|
* after all other lights have been enabled or disabled.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
set_ambient_light(const LColor &color) {
|
|
}
|
|
|
|
/**
|
|
* Intended to be overridden by a derived class to enable the indicated light
|
|
* id. A specific Light will already have been bound to this id via
|
|
* bind_light().
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
enable_light(int light_id, bool enable) {
|
|
}
|
|
|
|
/**
|
|
* Called immediately before bind_light() is called, this is intended to
|
|
* provide the derived class a hook in which to set up some state (like
|
|
* transform) that might apply to several lights.
|
|
*
|
|
* The sequence is: begin_bind_lights() will be called, then one or more
|
|
* bind_light() calls, then end_bind_lights().
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
begin_bind_lights() {
|
|
}
|
|
|
|
/**
|
|
* Called after before bind_light() has been called one or more times (but
|
|
* before any geometry is issued or additional state is changed), this is
|
|
* intended to clean up any temporary changes to the state that may have been
|
|
* made by begin_bind_lights().
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
end_bind_lights() {
|
|
}
|
|
|
|
/**
|
|
* Intended to be overridden by a derived class to enable or disable the use
|
|
* of clipping planes overall. This is called by do_issue_clip_plane()
|
|
* according to whether any planes are in use or not.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
enable_clip_planes(bool enable) {
|
|
}
|
|
|
|
/**
|
|
* Intended to be overridden by a derived class to enable the indicated plane
|
|
* id. A specific PlaneNode will already have been bound to this id via
|
|
* bind_clip_plane().
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
enable_clip_plane(int plane_id, bool enable) {
|
|
}
|
|
|
|
/**
|
|
* Called immediately before bind_clip_plane() is called, this is intended to
|
|
* provide the derived class a hook in which to set up some state (like
|
|
* transform) that might apply to several planes.
|
|
*
|
|
* The sequence is: begin_bind_clip_planes() will be called, then one or more
|
|
* bind_clip_plane() calls, then end_bind_clip_planes().
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
begin_bind_clip_planes() {
|
|
}
|
|
|
|
/**
|
|
* Called the first time a particular clipping plane has been bound to a given
|
|
* id within a frame, this should set up the associated hardware (or API)
|
|
* clipping plane with the plane's properties.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
bind_clip_plane(const NodePath &plane, int plane_id) {
|
|
}
|
|
|
|
/**
|
|
* Called after before bind_clip_plane() has been called one or more times
|
|
* (but before any geometry is issued or additional state is changed), this is
|
|
* intended to clean up any temporary changes to the state that may have been
|
|
* made by begin_bind_clip_planes().
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
end_bind_clip_planes() {
|
|
}
|
|
|
|
/**
|
|
* Assigns _target_texture and _target_tex_gen based on the _target_rs.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
determine_target_texture() {
|
|
const TextureAttrib *target_texture = (const TextureAttrib *)
|
|
_target_rs->get_attrib_def(TextureAttrib::get_class_slot());
|
|
const TexGenAttrib *target_tex_gen = (const TexGenAttrib *)
|
|
_target_rs->get_attrib_def(TexGenAttrib::get_class_slot());
|
|
|
|
nassertv(target_texture != (TextureAttrib *)NULL &&
|
|
target_tex_gen != (TexGenAttrib *)NULL);
|
|
_target_texture = target_texture;
|
|
_target_tex_gen = target_tex_gen;
|
|
|
|
if (_has_texture_alpha_scale) {
|
|
PT(TextureStage) stage = get_alpha_scale_texture_stage();
|
|
PT(Texture) texture = TexturePool::get_alpha_scale_map();
|
|
|
|
_target_texture = DCAST(TextureAttrib, _target_texture->add_on_stage(stage, texture));
|
|
_target_tex_gen = DCAST(TexGenAttrib, _target_tex_gen->add_stage
|
|
(stage, TexGenAttrib::M_constant, LTexCoord3(_current_color_scale[3], 0.0f, 0.0f)));
|
|
}
|
|
|
|
int max_texture_stages = get_max_texture_stages();
|
|
_target_texture = _target_texture->filter_to_max(max_texture_stages);
|
|
nassertv(_target_texture->get_num_on_stages() <= max_texture_stages);
|
|
}
|
|
|
|
/**
|
|
* Frees some memory that was explicitly allocated within the glgsg.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
free_pointers() {
|
|
}
|
|
|
|
/**
|
|
* This is called by the associated GraphicsWindow when close_window() is
|
|
* called. It should null out the _win pointer and possibly free any open
|
|
* resources associated with the GSG.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
close_gsg() {
|
|
// Protect from multiple calls, and also inform any other functions not to
|
|
// try to create new stuff while we're going down.
|
|
if (_closing_gsg) {
|
|
return;
|
|
}
|
|
_closing_gsg = true;
|
|
|
|
if (display_cat.is_debug()) {
|
|
display_cat.debug()
|
|
<< this << " close_gsg " << get_type() << "\n";
|
|
}
|
|
|
|
// As tempting as it may be to try to release all the textures and geoms
|
|
// now, we can't, because we might not be the currently-active GSG (this is
|
|
// particularly important in OpenGL, which maintains one currently-active GL
|
|
// state in each thread). If we start deleting textures, we'll be
|
|
// inadvertently deleting textures from some other OpenGL state.
|
|
|
|
// Fortunately, it doesn't really matter, since the graphics API will be
|
|
// responsible for cleaning up anything we don't clean up explicitly. We'll
|
|
// just let them drop.
|
|
|
|
// Make sure that all the contexts belonging to the GSG are deleted.
|
|
_prepared_objects.clear();
|
|
#ifdef DO_PSTATS
|
|
_pending_timer_queries.clear();
|
|
#endif
|
|
|
|
free_pointers();
|
|
}
|
|
|
|
/**
|
|
* This is called internally when it is determined that things are just fubar.
|
|
* It temporarily deactivates the GSG just so things don't get out of hand,
|
|
* and throws an event so the application can deal with this if it needs to.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
panic_deactivate() {
|
|
if (_active) {
|
|
display_cat.error()
|
|
<< "Deactivating " << get_type() << ".\n";
|
|
set_active(false);
|
|
throw_event("panic-deactivate-gsg", this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called whenever the color or color scale is changed, if
|
|
* _color_scale_via_lighting is true. This will rederive
|
|
* _material_force_color and _light_color_scale appropriately.
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
determine_light_color_scale() {
|
|
if (_has_scene_graph_color) {
|
|
// If we have a scene graph color, it, plus the color scale, goes directly
|
|
// into the material; we don't color scale the lights--this allows an
|
|
// alpha color scale to work properly.
|
|
_has_material_force_color = true;
|
|
_material_force_color = _scene_graph_color;
|
|
_light_color_scale.set(1.0f, 1.0f, 1.0f, 1.0f);
|
|
if (!_color_blend_involves_color_scale && _color_scale_enabled) {
|
|
_material_force_color.set(_scene_graph_color[0] * _current_color_scale[0],
|
|
_scene_graph_color[1] * _current_color_scale[1],
|
|
_scene_graph_color[2] * _current_color_scale[2],
|
|
_scene_graph_color[3] * _current_color_scale[3]);
|
|
}
|
|
|
|
} else {
|
|
// Otherise, leave the materials alone, but we might still scale the
|
|
// lights.
|
|
_has_material_force_color = false;
|
|
_light_color_scale.set(1.0f, 1.0f, 1.0f, 1.0f);
|
|
if (!_color_blend_involves_color_scale && _color_scale_enabled) {
|
|
_light_color_scale = _current_color_scale;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
CPT(RenderState) GraphicsStateGuardian::
|
|
get_unlit_state() {
|
|
static CPT(RenderState) state = NULL;
|
|
if (state == (const RenderState *)NULL) {
|
|
state = RenderState::make(LightAttrib::make_all_off());
|
|
}
|
|
return state;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
CPT(RenderState) GraphicsStateGuardian::
|
|
get_unclipped_state() {
|
|
static CPT(RenderState) state = NULL;
|
|
if (state == (const RenderState *)NULL) {
|
|
state = RenderState::make(ClipPlaneAttrib::make_all_off());
|
|
}
|
|
return state;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
CPT(RenderState) GraphicsStateGuardian::
|
|
get_untextured_state() {
|
|
static CPT(RenderState) state = NULL;
|
|
if (state == (const RenderState *)NULL) {
|
|
state = RenderState::make(TextureAttrib::make_off());
|
|
}
|
|
return state;
|
|
}
|
|
|
|
/**
|
|
* Should be called when a texture is encountered that needs to have its RAM
|
|
* image reloaded, and get_incomplete_render() is true. This will fire off a
|
|
* thread on the current Loader object that will request the texture to load
|
|
* its image. The image will be available at some point in the future (no
|
|
* event will be generated).
|
|
*/
|
|
void GraphicsStateGuardian::
|
|
async_reload_texture(TextureContext *tc) {
|
|
nassertv(_loader != (Loader *)NULL);
|
|
|
|
int priority = 0;
|
|
if (_current_display_region != (DisplayRegion *)NULL) {
|
|
priority = _current_display_region->get_texture_reload_priority();
|
|
}
|
|
|
|
string task_name = string("reload:") + tc->get_texture()->get_name();
|
|
PT(AsyncTaskManager) task_mgr = _loader->get_task_manager();
|
|
|
|
// See if we are already loading this task.
|
|
AsyncTaskCollection orig_tasks = task_mgr->find_tasks(task_name);
|
|
int num_tasks = orig_tasks.get_num_tasks();
|
|
for (int ti = 0; ti < num_tasks; ++ti) {
|
|
AsyncTask *task = orig_tasks.get_task(ti);
|
|
if (task->is_exact_type(TextureReloadRequest::get_class_type()) &&
|
|
DCAST(TextureReloadRequest, task)->get_texture() == tc->get_texture()) {
|
|
// This texture is already queued to be reloaded. Don't queue it again,
|
|
// just make sure the priority is updated, and return.
|
|
task->set_priority(max(task->get_priority(), priority));
|
|
return;
|
|
}
|
|
}
|
|
|
|
// This texture has not yet been queued to be reloaded. Queue it up now.
|
|
PT(AsyncTask) request =
|
|
new TextureReloadRequest(task_name,
|
|
_prepared_objects, tc->get_texture(),
|
|
_supports_compressed_texture);
|
|
request->set_priority(priority);
|
|
_loader->load_async(request);
|
|
}
|
|
|
|
/**
|
|
* Returns a shadow map for the given light source. If none exists, it is
|
|
* created, using the given host window to create the buffer, or the current
|
|
* window if that is set to NULL.
|
|
*/
|
|
PT(Texture) GraphicsStateGuardian::
|
|
get_shadow_map(const NodePath &light_np, GraphicsOutputBase *host) {
|
|
nassertr(light_np.node()->is_of_type(DirectionalLight::get_class_type()) ||
|
|
light_np.node()->is_of_type(PointLight::get_class_type()) ||
|
|
light_np.node()->is_of_type(Spotlight::get_class_type()), NULL);
|
|
|
|
PT(LightLensNode) light = DCAST(LightLensNode, light_np.node());
|
|
if (light == NULL || !light->_shadow_caster) {
|
|
// TODO: return dummy shadow map (all white).
|
|
return NULL;
|
|
}
|
|
|
|
// See if we already have a buffer. If not, create one.
|
|
if (light->_sbuffers.count(this) == 0) {
|
|
if (host == (GraphicsOutputBase *)NULL) {
|
|
host = _current_display_region->get_window();
|
|
}
|
|
nassertr(host != NULL, NULL);
|
|
|
|
// Nope, the light doesn't have a buffer for our GSG. Make one.
|
|
return make_shadow_buffer(light_np, host);
|
|
|
|
} else {
|
|
// There's already a buffer - use that.
|
|
return light->_sbuffers[this]->get_texture();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a depth buffer for shadow mapping. This is a convenience function
|
|
* for the ShaderGenerator; putting this directly in the ShaderGenerator would
|
|
* cause circular dependency issues. Returns the depth texture.
|
|
*/
|
|
PT(Texture) GraphicsStateGuardian::
|
|
make_shadow_buffer(const NodePath &light_np, GraphicsOutputBase *host) {
|
|
// Make sure everything is valid.
|
|
nassertr(light_np.node()->is_of_type(DirectionalLight::get_class_type()) ||
|
|
light_np.node()->is_of_type(PointLight::get_class_type()) ||
|
|
light_np.node()->is_of_type(Spotlight::get_class_type()), NULL);
|
|
|
|
PT(LightLensNode) light = DCAST(LightLensNode, light_np.node());
|
|
if (light == NULL || !light->_shadow_caster) {
|
|
return NULL;
|
|
}
|
|
|
|
bool is_point = light->is_of_type(PointLight::get_class_type());
|
|
|
|
nassertr(light->_sbuffers.count(this) == 0, NULL);
|
|
|
|
if (display_cat.is_debug()) {
|
|
display_cat.debug()
|
|
<< "Constructing shadow buffer for light '" << light->get_name()
|
|
<< "', size=" << light->_sb_size[0] << "x" << light->_sb_size[1]
|
|
<< ", sort=" << light->_sb_sort << "\n";
|
|
}
|
|
|
|
// Determine the properties for creating the depth buffer.
|
|
FrameBufferProperties fbp;
|
|
fbp.set_depth_bits(shadow_depth_bits);
|
|
|
|
WindowProperties props = WindowProperties::size(light->_sb_size[0], light->_sb_size[1]);
|
|
int flags = GraphicsPipe::BF_refuse_window;
|
|
if (is_point) {
|
|
flags |= GraphicsPipe::BF_size_square;
|
|
}
|
|
|
|
// Create the buffer
|
|
PT(GraphicsOutput) sbuffer = get_engine()->make_output(get_pipe(), light->get_name(),
|
|
light->_sb_sort, fbp, props, flags, this, DCAST(GraphicsOutput, host));
|
|
nassertr(sbuffer != NULL, NULL);
|
|
|
|
// Create a texture and fill it in with some data to workaround an OpenGL
|
|
// error
|
|
PT(Texture) tex = new Texture(light->get_name());
|
|
if (is_point) {
|
|
if (light->_sb_size[0] != light->_sb_size[1]) {
|
|
display_cat.error()
|
|
<< "PointLight shadow buffers must have an equal width and height!\n";
|
|
}
|
|
tex->setup_cube_map(light->_sb_size[0], Texture::T_unsigned_byte, Texture::F_depth_component);
|
|
} else {
|
|
tex->setup_2d_texture(light->_sb_size[0], light->_sb_size[1], Texture::T_unsigned_byte, Texture::F_depth_component);
|
|
}
|
|
tex->make_ram_image();
|
|
sbuffer->add_render_texture(tex, GraphicsOutput::RTM_bind_or_copy, GraphicsOutput::RTP_depth);
|
|
|
|
// Set the wrap mode
|
|
if (is_point) {
|
|
tex->set_wrap_u(SamplerState::WM_clamp);
|
|
tex->set_wrap_v(SamplerState::WM_clamp);
|
|
} else {
|
|
tex->set_wrap_u(SamplerState::WM_border_color);
|
|
tex->set_wrap_v(SamplerState::WM_border_color);
|
|
tex->set_border_color(LVecBase4(1, 1, 1, 1));
|
|
}
|
|
|
|
// Note: cube map shadow filtering doesn't seem to work in Cg.
|
|
if (get_supports_shadow_filter() && !is_point) {
|
|
// If we have the ARB_shadow extension, enable shadow filtering.
|
|
tex->set_minfilter(SamplerState::FT_shadow);
|
|
tex->set_magfilter(SamplerState::FT_shadow);
|
|
} else {
|
|
tex->set_minfilter(SamplerState::FT_linear);
|
|
tex->set_magfilter(SamplerState::FT_linear);
|
|
}
|
|
|
|
// Assign display region(s) to the buffer and camera
|
|
if (is_point) {
|
|
for (int i = 0; i < 6; ++i) {
|
|
PT(DisplayRegion) dr = sbuffer->make_mono_display_region(0, 1, 0, 1);
|
|
dr->set_lens_index(i);
|
|
dr->set_target_tex_page(i);
|
|
dr->set_camera(light_np);
|
|
dr->set_clear_depth_active(true);
|
|
}
|
|
} else {
|
|
PT(DisplayRegion) dr = sbuffer->make_mono_display_region(0, 1, 0, 1);
|
|
dr->set_camera(light_np);
|
|
dr->set_clear_depth_active(true);
|
|
}
|
|
light->_sbuffers[this] = sbuffer;
|
|
|
|
return tex;
|
|
}
|
|
|
|
/**
|
|
* Returns true if the GSG implements the extension identified by the given
|
|
* string. This currently is only implemented by the OpenGL back-end.
|
|
*/
|
|
bool GraphicsStateGuardian::
|
|
has_extension(const string &extension) const {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns the vendor of the video card driver
|
|
*/
|
|
string GraphicsStateGuardian::
|
|
get_driver_vendor() {
|
|
return string();
|
|
}
|
|
|
|
/**
|
|
* Returns GL_Renderer
|
|
*/
|
|
string GraphicsStateGuardian::get_driver_renderer() {
|
|
return string();
|
|
}
|
|
|
|
/**
|
|
* Returns driver version This has an implementation-defined meaning, and may
|
|
* be "" if the particular graphics implementation does not provide a way to
|
|
* query this information.
|
|
*/
|
|
string GraphicsStateGuardian::
|
|
get_driver_version() {
|
|
return string();
|
|
}
|
|
|
|
/**
|
|
* Returns major version of the video driver. This has an implementation-
|
|
* defined meaning, and may be -1 if the particular graphics implementation
|
|
* does not provide a way to query this information.
|
|
*/
|
|
int GraphicsStateGuardian::
|
|
get_driver_version_major() {
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Returns the minor version of the video driver. This has an implementation-
|
|
* defined meaning, and may be -1 if the particular graphics implementation
|
|
* does not provide a way to query this information.
|
|
*/
|
|
int GraphicsStateGuardian::
|
|
get_driver_version_minor() {
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Returns the major version of the shader model.
|
|
*/
|
|
int GraphicsStateGuardian::
|
|
get_driver_shader_version_major() {
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Returns the minor version of the shader model.
|
|
*/
|
|
int GraphicsStateGuardian::
|
|
get_driver_shader_version_minor() {
|
|
return -1;
|
|
}
|
|
|
|
ostream &
|
|
operator << (ostream &out, GraphicsStateGuardian::ShaderModel sm) {
|
|
static const char *sm_strings[] = {"none", "1.1", "2.0", "2.x", "3.0", "4.0", "5.0", "5.1"};
|
|
nassertr(sm >= 0 && sm <= GraphicsStateGuardian::SM_51, out);
|
|
out << sm_strings[sm];
|
|
return out;
|
|
}
|