/** * 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 tinyGraphicsStateGuardian.cxx * @author drose * @date 2008-04-24 */ #include "tinyGraphicsStateGuardian.h" #include "tinyGeomMunger.h" #include "tinyTextureContext.h" #include "config_tinydisplay.h" #include "pStatTimer.h" #include "geomVertexReader.h" #include "ambientLight.h" #include "pointLight.h" #include "directionalLight.h" #include "spotlight.h" #include "depthWriteAttrib.h" #include "depthOffsetAttrib.h" #include "colorWriteAttrib.h" #include "alphaTestAttrib.h" #include "depthTestAttrib.h" #include "shadeModelAttrib.h" #include "cullFaceAttrib.h" #include "rescaleNormalAttrib.h" #include "materialAttrib.h" #include "lightAttrib.h" #include "scissorAttrib.h" #include "bitMask.h" #include "samplerState.h" #include "zgl.h" #include "zmath.h" #include "ztriangle_table.h" #include "store_pixel_table.h" #include "graphicsEngine.h" TypeHandle TinyGraphicsStateGuardian::_type_handle; PStatCollector TinyGraphicsStateGuardian::_vertices_immediate_pcollector("Vertices:Immediate mode"); PStatCollector TinyGraphicsStateGuardian::_draw_transform_pcollector("Draw:Transform"); PStatCollector TinyGraphicsStateGuardian::_pixel_count_white_untextured_pcollector("Pixels:White untextured"); PStatCollector TinyGraphicsStateGuardian::_pixel_count_flat_untextured_pcollector("Pixels:Flat untextured"); PStatCollector TinyGraphicsStateGuardian::_pixel_count_smooth_untextured_pcollector("Pixels:Smooth untextured"); PStatCollector TinyGraphicsStateGuardian::_pixel_count_white_textured_pcollector("Pixels:White textured"); PStatCollector TinyGraphicsStateGuardian::_pixel_count_flat_textured_pcollector("Pixels:Flat textured"); PStatCollector TinyGraphicsStateGuardian::_pixel_count_smooth_textured_pcollector("Pixels:Smooth textured"); PStatCollector TinyGraphicsStateGuardian::_pixel_count_white_perspective_pcollector("Pixels:White perspective"); PStatCollector TinyGraphicsStateGuardian::_pixel_count_flat_perspective_pcollector("Pixels:Flat perspective"); PStatCollector TinyGraphicsStateGuardian::_pixel_count_smooth_perspective_pcollector("Pixels:Smooth perspective"); PStatCollector TinyGraphicsStateGuardian::_pixel_count_smooth_multitex2_pcollector("Pixels:Smooth multitex 2"); PStatCollector TinyGraphicsStateGuardian::_pixel_count_smooth_multitex3_pcollector("Pixels:Smooth multitex 3"); /** * */ TinyGraphicsStateGuardian:: TinyGraphicsStateGuardian(GraphicsEngine *engine, GraphicsPipe *pipe, TinyGraphicsStateGuardian *share_with) : GraphicsStateGuardian(CS_yup_right, engine, pipe) { _current_frame_buffer = NULL; _aux_frame_buffer = NULL; _c = NULL; _vertices = NULL; _vertices_size = 0; } /** * */ TinyGraphicsStateGuardian:: ~TinyGraphicsStateGuardian() { } /** * Resets all internal state as if the gsg were newly created. */ void TinyGraphicsStateGuardian:: reset() { free_pointers(); GraphicsStateGuardian::reset(); // Build _inv_state_mask as a mask of 1's where we don't care, and 0's where // we do care, about the state. _inv_state_mask.clear_bit(ColorAttrib::get_class_slot()); _inv_state_mask.clear_bit(ColorScaleAttrib::get_class_slot()); _inv_state_mask.clear_bit(CullFaceAttrib::get_class_slot()); _inv_state_mask.clear_bit(DepthOffsetAttrib::get_class_slot()); _inv_state_mask.clear_bit(RenderModeAttrib::get_class_slot()); _inv_state_mask.clear_bit(RescaleNormalAttrib::get_class_slot()); _inv_state_mask.clear_bit(TextureAttrib::get_class_slot()); _inv_state_mask.clear_bit(MaterialAttrib::get_class_slot()); _inv_state_mask.clear_bit(LightAttrib::get_class_slot()); _inv_state_mask.clear_bit(ScissorAttrib::get_class_slot()); if (_c != (GLContext *)NULL) { glClose(_c); _c = NULL; } _c = (GLContext *)gl_zalloc(sizeof(GLContext)); glInit(_c, _current_frame_buffer); _c->draw_triangle_front = gl_draw_triangle_fill; _c->draw_triangle_back = gl_draw_triangle_fill; _supported_geom_rendering = Geom::GR_point | Geom::GR_indexed_other | Geom::GR_triangle_strip | Geom::GR_flat_last_vertex | Geom::GR_render_mode_wireframe | Geom::GR_render_mode_point; _max_texture_dimension = (1 << ZB_POINT_ST_FRAC_BITS); _max_texture_stages = MAX_TEXTURE_STAGES; _max_lights = MAX_LIGHTS; _color_scale_via_lighting = false; _alpha_scale_via_texture = false; _runtime_color_scale = true; _color_material_flags = 0; _texturing_state = 0; _texfilter_state = 0; _texture_replace = false; _filled_flat = false; _auto_rescale_normal = false; // Now that the GSG has been initialized, make it available for // optimizations. add_gsg(this); } /** * Frees some memory that was explicitly allocated within the glgsg. */ void TinyGraphicsStateGuardian:: free_pointers() { if (_aux_frame_buffer != (ZBuffer *)NULL) { ZB_close(_aux_frame_buffer); _aux_frame_buffer = NULL; } if (_vertices != (GLVertex *)NULL) { PANDA_FREE_ARRAY(_vertices); _vertices = NULL; } _vertices_size = 0; } /** * 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 TinyGraphicsStateGuardian:: close_gsg() { GraphicsStateGuardian::close_gsg(); if (_c != (GLContext *)NULL) { glClose(_c); _c = NULL; } } /** * 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 TinyGraphicsStateGuardian:: depth_offset_decals() { return false; } /** * Creates a new GeomMunger object to munge vertices appropriate to this GSG * for the indicated state. */ PT(GeomMunger) TinyGraphicsStateGuardian:: make_geom_munger(const RenderState *state, Thread *current_thread) { PT(TinyGeomMunger) munger = new TinyGeomMunger(this, state); return GeomMunger::register_munger(munger, current_thread); } /** * 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 TinyGraphicsStateGuardian:: clear(DrawableRegion *clearable) { PStatTimer timer(_clear_pcollector); if ((!clearable->get_clear_color_active())&& (!clearable->get_clear_depth_active())&& (!clearable->get_clear_stencil_active())) { return; } set_state_and_transform(RenderState::make_empty(), _internal_transform); bool clear_color = false; PIXEL color = 0; if (clearable->get_clear_color_active()) { LColor v = clearable->get_clear_color(); v = v.fmin(LColor(1, 1, 1, 1)).fmax(LColor::zero()); if (_current_properties->get_srgb_color()) { color = SRGBA_TO_PIXEL( (v[0] * 0xffff), (v[1] * 0xffff), (v[2] * 0xffff), (v[3] * 0xffff)); } else { color = RGBA_TO_PIXEL( (v[0] * 0xffff), (v[1] * 0xffff), (v[2] * 0xffff), (v[3] * 0xffff)); } clear_color = true; } bool clear_z = false; int z = 0; if (clearable->get_clear_depth_active()) { // We ignore the specified depth clear value, since we don't support // alternate depth compare functions anyway. clear_z = true; } ZB_clear_viewport(_c->zb, clear_z, z, clear_color, color, _c->viewport.xmin, _c->viewport.ymin, _c->viewport.xsize, _c->viewport.ysize); } /** * Prepare a display region for rendering (set up scissor region and viewport) */ void TinyGraphicsStateGuardian:: prepare_display_region(DisplayRegionPipelineReader *dr) { nassertv(dr != (DisplayRegionPipelineReader *)NULL); GraphicsStateGuardian::prepare_display_region(dr); int xmin, ymin, xsize, ysize; dr->get_region_pixels_i(xmin, ymin, xsize, ysize); PN_stdfloat pixel_factor = _current_display_region->get_pixel_factor(); if (pixel_factor != 1.0) { // Render into an aux buffer, and zoom it up into the main frame buffer // later. xmin = 0; ymin = 0; xsize = int(xsize * pixel_factor); ysize = int(ysize * pixel_factor); if (_aux_frame_buffer == (ZBuffer *)NULL) { _aux_frame_buffer = ZB_open(xsize, ysize, ZB_MODE_RGBA, 0, 0, 0, 0); } else if (_aux_frame_buffer->xsize < xsize || _aux_frame_buffer->ysize < ysize) { ZB_resize(_aux_frame_buffer, NULL, max(_aux_frame_buffer->xsize, xsize), max(_aux_frame_buffer->ysize, ysize)); } _c->zb = _aux_frame_buffer; } else { // Render directly into the main frame buffer. _c->zb = _current_frame_buffer; } _c->viewport.xmin = xmin; _c->viewport.ymin = ymin; _c->viewport.xsize = xsize; _c->viewport.ysize = ysize; set_scissor(0.0f, 1.0f, 0.0f, 1.0f); nassertv(xmin >= 0 && xmin < _c->zb->xsize && ymin >= 0 && ymin < _c->zb->ysize && xmin + xsize >= 0 && xmin + xsize <= _c->zb->xsize && ymin + ysize >= 0 && ymin + ysize <= _c->zb->ysize); } /** * Given a lens, calculates the appropriate projection matrix for use with * this gsg. Note that the projection matrix depends a lot upon the * coordinate system of the rendering API. * * The return value is a TransformState if the lens is acceptable, NULL if it * is not. */ CPT(TransformState) TinyGraphicsStateGuardian:: calc_projection_mat(const Lens *lens) { if (lens == (Lens *)NULL) { return NULL; } if (!lens->is_linear()) { return NULL; } // The projection matrix must always be right-handed Y-up, even if our // coordinate system of choice is otherwise, because certain GL calls // (specifically glTexGen(GL_SPHERE_MAP)) assume this kind of a coordinate // system. Sigh. In order to implement a Z-up (or other arbitrary) // coordinate system, we'll use a Y-up projection matrix, and store the // conversion to our coordinate system of choice in the modelview matrix. LMatrix4 result = LMatrix4::convert_mat(CS_yup_right, _current_lens->get_coordinate_system()) * lens->get_projection_mat(_current_stereo_channel); if (_scene_setup->get_inverted()) { // If the scene is supposed to be inverted, then invert the projection // matrix. result *= LMatrix4::scale_mat(1.0f, -1.0f, 1.0f); } return TransformState::make_mat(result); } /** * 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 TinyGraphicsStateGuardian:: prepare_lens() { _transform_stale = true; return true; } /** * 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 TinyGraphicsStateGuardian:: begin_frame(Thread *current_thread) { if (!GraphicsStateGuardian::begin_frame(current_thread)) { return false; } _c->zb = _current_frame_buffer; #ifdef DO_PSTATS _vertices_immediate_pcollector.clear_level(); _pixel_count_white_untextured_pcollector.clear_level(); _pixel_count_flat_untextured_pcollector.clear_level(); _pixel_count_smooth_untextured_pcollector.clear_level(); _pixel_count_white_textured_pcollector.clear_level(); _pixel_count_flat_textured_pcollector.clear_level(); _pixel_count_smooth_textured_pcollector.clear_level(); _pixel_count_white_perspective_pcollector.clear_level(); _pixel_count_flat_perspective_pcollector.clear_level(); _pixel_count_smooth_perspective_pcollector.clear_level(); _pixel_count_smooth_multitex2_pcollector.clear_level(); _pixel_count_smooth_multitex3_pcollector.clear_level(); #endif return true; } /** * 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(). * * 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 TinyGraphicsStateGuardian:: begin_scene() { return GraphicsStateGuardian::begin_scene(); } /** * 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 TinyGraphicsStateGuardian:: end_scene() { if (_c->zb == _aux_frame_buffer) { // Copy the aux frame buffer into the main scene now, zooming it up to the // appropriate size. int xmin, ymin, xsize, ysize; _current_display_region->get_region_pixels_i(xmin, ymin, xsize, ysize); PN_stdfloat pixel_factor = _current_display_region->get_pixel_factor(); int fb_xsize = int(xsize * pixel_factor); int fb_ysize = int(ysize * pixel_factor); ZB_zoomFrameBuffer(_current_frame_buffer, xmin, ymin, xsize, ysize, _aux_frame_buffer, 0, 0, fb_xsize, fb_ysize); _c->zb = _current_frame_buffer; } // Clear the lighting state. clear_light_state(); _plights.clear(); _dlights.clear(); _slights.clear(); GraphicsStateGuardian::end_scene(); } /** * 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 TinyGraphicsStateGuardian:: end_frame(Thread *current_thread) { GraphicsStateGuardian::end_frame(current_thread); #ifndef NDEBUG static ConfigVariableBool td_show_zbuffer ("td-show-zbuffer", false, PRC_DESC("Set this true to draw the ZBuffer instead of the visible buffer, when rendering with tinydisplay. This is useful to aid debugging the ZBuffer")); if (td_show_zbuffer) { PIXEL *tp = _current_frame_buffer->pbuf; ZPOINT *tz = _current_frame_buffer->zbuf; for (int yi = 0; yi < _current_frame_buffer->ysize; ++yi) { for (int xi = 0; xi < _current_frame_buffer->xsize; ++xi) { (*tp) = (int)(*tz); ++tz; ++tp; } } } #endif // NDEBUG #ifdef DO_PSTATS // Flush any PCollectors specific to this kind of GSG. _vertices_immediate_pcollector.flush_level(); _pixel_count_white_untextured_pcollector.flush_level(); _pixel_count_flat_untextured_pcollector.flush_level(); _pixel_count_smooth_untextured_pcollector.flush_level(); _pixel_count_white_textured_pcollector.flush_level(); _pixel_count_flat_textured_pcollector.flush_level(); _pixel_count_smooth_textured_pcollector.flush_level(); _pixel_count_white_perspective_pcollector.flush_level(); _pixel_count_flat_perspective_pcollector.flush_level(); _pixel_count_smooth_perspective_pcollector.flush_level(); _pixel_count_smooth_multitex2_pcollector.flush_level(); _pixel_count_smooth_multitex3_pcollector.flush_level(); #endif // DO_PSTATS } /** * 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 TinyGraphicsStateGuardian:: begin_draw_primitives(const GeomPipelineReader *geom_reader, const GeomMunger *munger, const GeomVertexDataPipelineReader *data_reader, bool force) { #ifndef NDEBUG if (tinydisplay_cat.is_spam()) { tinydisplay_cat.spam() << "begin_draw_primitives: " << *(data_reader->get_object()) << "\n"; } #endif // NDEBUG if (!GraphicsStateGuardian::begin_draw_primitives(geom_reader, munger, data_reader, force)) { return false; } nassertr(_data_reader != (GeomVertexDataPipelineReader *)NULL, false); PStatTimer timer(_draw_transform_pcollector); // Set up the proper transform. if (_data_reader->is_vertex_transformed()) { // If the vertex data claims to be already transformed into clip // coordinates, wipe out the current projection and modelview matrix (so // we don't attempt to transform it again). const TransformState *ident = TransformState::make_identity(); load_matrix(&_c->matrix_model_view, ident); load_matrix(&_c->matrix_projection, _scissor_mat); load_matrix(&_c->matrix_model_view_inv, ident); load_matrix(&_c->matrix_model_projection, _scissor_mat); _c->matrix_model_projection_no_w_transform = 1; _transform_stale = true; } else if (_transform_stale) { // Load the actual transform. CPT(TransformState) scissor_proj_mat = _scissor_mat->compose(_projection_mat); if (_c->lighting_enabled) { // With the lighting equation, we need to keep the modelview and // projection matrices separate. load_matrix(&_c->matrix_model_view, _internal_transform); load_matrix(&_c->matrix_projection, scissor_proj_mat); /* precompute inverse modelview */ M4 tmp; gl_M4_Inv(&tmp, &_c->matrix_model_view); gl_M4_Transpose(&_c->matrix_model_view_inv, &tmp); } // Compose the modelview and projection matrices. load_matrix(&_c->matrix_model_projection, scissor_proj_mat->compose(_internal_transform)); /* test to accelerate computation */ _c->matrix_model_projection_no_w_transform = 0; PN_stdfloat *m = &_c->matrix_model_projection.m[0][0]; if (m[12] == 0.0 && m[13] == 0.0 && m[14] == 0.0) { _c->matrix_model_projection_no_w_transform = 1; } _transform_stale = false; } // Figure out the subset of vertices we will be using in this operation. int num_vertices = data_reader->get_num_rows(); _min_vertex = num_vertices; _max_vertex = 0; int num_prims = geom_reader->get_num_primitives(); int i; for (i = 0; i < num_prims; ++i) { CPT(GeomPrimitive) prim = geom_reader->get_primitive(i); int nv = prim->get_min_vertex(); _min_vertex = min(_min_vertex, nv); int xv = prim->get_max_vertex(); _max_vertex = max(_max_vertex, xv); } if (_min_vertex > _max_vertex) { return false; } // Now copy all of those vertices into our working table, transforming into // screen space them as we go. int num_used_vertices = _max_vertex - _min_vertex + 1; if (_vertices_size < num_used_vertices) { if (_vertices_size == 0) { _vertices_size = 1; } while (_vertices_size < num_used_vertices) { _vertices_size *= 2; } if (_vertices != (GLVertex *)NULL) { PANDA_FREE_ARRAY(_vertices); } _vertices = (GLVertex *)PANDA_MALLOC_ARRAY(_vertices_size * sizeof(GLVertex)); } GeomVertexReader rcolor, rnormal; // We now support up to 3-stage multitexturing. GenTexcoordFunc *texgen_func[MAX_TEXTURE_STAGES]; TexCoordData tcdata[MAX_TEXTURE_STAGES]; const TexGenAttrib *target_tex_gen = DCAST(TexGenAttrib, _target_rs->get_attrib_def(TexGenAttrib::get_class_slot())); const TexMatrixAttrib *target_tex_matrix = DCAST(TexMatrixAttrib, _target_rs->get_attrib_def(TexMatrixAttrib::get_class_slot())); int max_stage_index = _target_texture->get_num_on_ff_stages(); for (int si = 0; si < max_stage_index; ++si) { TextureStage *stage = _target_texture->get_on_ff_stage(si); switch (target_tex_gen->get_mode(stage)) { case TexGenAttrib::M_eye_sphere_map: tcdata[si]._r1 = GeomVertexReader(data_reader, InternalName::get_normal(), force); tcdata[si]._r2 = GeomVertexReader(data_reader, InternalName::get_vertex(), force); texgen_func[si] = &texgen_sphere_map; tcdata[si]._mat = _internal_transform->get_mat(); break; case TexGenAttrib::M_eye_position: tcdata[si]._r1 = GeomVertexReader(data_reader, InternalName::get_vertex(), force); texgen_func[si] = &texgen_texmat; { CPT(TransformState) eye_transform = _cs_transform->invert_compose(_internal_transform); tcdata[si]._mat = eye_transform->get_mat(); } if (target_tex_matrix->has_stage(stage)) { tcdata[si]._mat = tcdata[si]._mat * target_tex_matrix->get_mat(stage); } break; case TexGenAttrib::M_world_position: tcdata[si]._r1 = GeomVertexReader(data_reader, InternalName::get_vertex(), force); texgen_func[si] = &texgen_texmat; { CPT(TransformState) render_transform = _cs_transform->compose(_scene_setup->get_world_transform()); CPT(TransformState) world_inv_transform = render_transform->invert_compose(_internal_transform); tcdata[si]._mat = world_inv_transform->get_mat(); } if (target_tex_matrix->has_stage(stage)) { tcdata[si]._mat = tcdata[si]._mat * target_tex_matrix->get_mat(stage); } break; default: // Fall through: use the standard texture coordinates. tcdata[si]._r1 = GeomVertexReader(data_reader, stage->get_texcoord_name(), force); texgen_func[si] = &texgen_simple; if (target_tex_matrix->has_stage(stage)) { texgen_func[si] = &texgen_texmat; tcdata[si]._mat = target_tex_matrix->get_mat(stage); } break; } tcdata[si]._r1.set_row_unsafe(_min_vertex); tcdata[si]._r2.set_row_unsafe(_min_vertex); if (!tcdata[si]._r1.has_column()) { texgen_func[si] = &texgen_null; } } bool needs_color = false; if (_vertex_colors_enabled) { rcolor = GeomVertexReader(data_reader, InternalName::get_color(), force); rcolor.set_row_unsafe(_min_vertex); needs_color = rcolor.has_column(); } if (!needs_color) { const LColor &d = _scene_graph_color; const LColor &s = _current_color_scale; _c->current_color.v[0] = max(d[0] * s[0], (PN_stdfloat)0); _c->current_color.v[1] = max(d[1] * s[1], (PN_stdfloat)0); _c->current_color.v[2] = max(d[2] * s[2], (PN_stdfloat)0); _c->current_color.v[3] = max(d[3] * s[3], (PN_stdfloat)0); } bool needs_normal = false; if (_c->lighting_enabled) { rnormal = GeomVertexReader(data_reader, InternalName::get_normal(), force); rnormal.set_row_unsafe(_min_vertex); needs_normal = rnormal.has_column(); } GeomVertexReader rvertex(data_reader, InternalName::get_vertex(), force); rvertex.set_row_unsafe(_min_vertex); if (!rvertex.has_column()) { // Whoops, guess the vertex data isn't resident. return false; } if (!needs_color && _color_material_flags) { if (_color_material_flags & CMF_ambient) { _c->materials[0].ambient = _c->current_color; _c->materials[1].ambient = _c->current_color; } if (_color_material_flags & CMF_diffuse) { _c->materials[0].diffuse = _c->current_color; _c->materials[1].diffuse = _c->current_color; } } if (_texturing_state != 0 && _texture_replace) { // We don't need the vertex color or lighting calculation after all, since // the current texture will just hide all of that. needs_color = false; needs_normal = false; } bool lighting_enabled = (needs_normal && _c->lighting_enabled); for (i = 0; i < num_used_vertices; ++i) { GLVertex *v = &_vertices[i]; const LVecBase4 &d = rvertex.get_data4(); v->coord.v[0] = d[0]; v->coord.v[1] = d[1]; v->coord.v[2] = d[2]; v->coord.v[3] = d[3]; // Texture coordinates. for (int si = 0; si < max_stage_index; ++si) { LTexCoord d; (*texgen_func[si])(v->tex_coord[si], tcdata[si]); } if (needs_color) { const LColor &d = rcolor.get_data4(); const LColor &s = _current_color_scale; _c->current_color.v[0] = max(d[0] * s[0], (PN_stdfloat)0); _c->current_color.v[1] = max(d[1] * s[1], (PN_stdfloat)0); _c->current_color.v[2] = max(d[2] * s[2], (PN_stdfloat)0); _c->current_color.v[3] = max(d[3] * s[3], (PN_stdfloat)0); if (_color_material_flags) { if (_color_material_flags & CMF_ambient) { _c->materials[0].ambient = _c->current_color; _c->materials[1].ambient = _c->current_color; } if (_color_material_flags & CMF_diffuse) { _c->materials[0].diffuse = _c->current_color; _c->materials[1].diffuse = _c->current_color; } } } v->color = _c->current_color; if (lighting_enabled) { const LVecBase3 &d = rnormal.get_data3(); _c->current_normal.v[0] = d[0]; _c->current_normal.v[1] = d[1]; _c->current_normal.v[2] = d[2]; _c->current_normal.v[3] = 0.0f; gl_vertex_transform(_c, v); gl_shade_vertex(_c, v); } else { gl_vertex_transform(_c, v); } if (v->clip_code == 0) { gl_transform_to_viewport(_c, v); } v->edge_flag = 1; } // Set up the appropriate function callback for filling triangles, according // to the current state. bool srgb_blend = _current_properties->get_srgb_color(); int depth_write_state = 0; // zon const DepthWriteAttrib *target_depth_write = DCAST(DepthWriteAttrib, _target_rs->get_attrib_def(DepthWriteAttrib::get_class_slot())); if (target_depth_write->get_mode() != DepthWriteAttrib::M_on) { depth_write_state = 1; // zoff } int color_write_state = 0; // cstore const ColorWriteAttrib *target_color_write = DCAST(ColorWriteAttrib, _target_rs->get_attrib_def(ColorWriteAttrib::get_class_slot())); unsigned int color_channels = target_color_write->get_channels() & _color_write_mask; if (color_channels == ColorWriteAttrib::C_all) { if (srgb_blend) { color_write_state = 4; // csstore } else { color_write_state = 0; // cstore } } else { // Implement a color mask. int op_a = get_color_blend_op(ColorBlendAttrib::O_one); int op_b = get_color_blend_op(ColorBlendAttrib::O_zero); if (srgb_blend) { _c->zb->store_pix_func = store_pixel_funcs_sRGB[op_a][op_b][color_channels]; } else { _c->zb->store_pix_func = store_pixel_funcs[op_a][op_b][color_channels]; } color_write_state = 2; // cgeneral } const TransparencyAttrib *target_transparency = DCAST(TransparencyAttrib, _target_rs->get_attrib_def(TransparencyAttrib::get_class_slot())); switch (target_transparency->get_mode()) { case TransparencyAttrib::M_alpha: case TransparencyAttrib::M_dual: if (color_channels == ColorWriteAttrib::C_all) { if (srgb_blend) { color_write_state = 5; // csblend } else { color_write_state = 1; // cblend } } else { // Implement a color mask, with alpha blending. int op_a = get_color_blend_op(ColorBlendAttrib::O_incoming_alpha); int op_b = get_color_blend_op(ColorBlendAttrib::O_one_minus_incoming_alpha); if (srgb_blend) { _c->zb->store_pix_func = store_pixel_funcs_sRGB[op_a][op_b][color_channels]; } else { _c->zb->store_pix_func = store_pixel_funcs[op_a][op_b][color_channels]; } color_write_state = 2; // cgeneral } break; case TransparencyAttrib::M_premultiplied_alpha: { // Implement a color mask, with pre-multiplied alpha blending. int op_a = get_color_blend_op(ColorBlendAttrib::O_one); int op_b = get_color_blend_op(ColorBlendAttrib::O_one_minus_incoming_alpha); if (srgb_blend) { _c->zb->store_pix_func = store_pixel_funcs_sRGB[op_a][op_b][color_channels]; } else { _c->zb->store_pix_func = store_pixel_funcs[op_a][op_b][color_channels]; } color_write_state = 2; // cgeneral } break; default: break; } const ColorBlendAttrib *target_color_blend = DCAST(ColorBlendAttrib, _target_rs->get_attrib_def(ColorBlendAttrib::get_class_slot())); if (target_color_blend->get_mode() == ColorBlendAttrib::M_add) { // If we have a color blend set that we can support, it overrides the // transparency set. LColor c = target_color_blend->get_color(); _c->zb->blend_r = (int)(c[0] * ZB_POINT_RED_MAX); _c->zb->blend_g = (int)(c[1] * ZB_POINT_GREEN_MAX); _c->zb->blend_b = (int)(c[2] * ZB_POINT_BLUE_MAX); _c->zb->blend_a = (int)(c[3] * ZB_POINT_ALPHA_MAX); int op_a = get_color_blend_op(target_color_blend->get_operand_a()); int op_b = get_color_blend_op(target_color_blend->get_operand_b()); if (srgb_blend) { _c->zb->store_pix_func = store_pixel_funcs_sRGB[op_a][op_b][color_channels]; } else { _c->zb->store_pix_func = store_pixel_funcs[op_a][op_b][color_channels]; } color_write_state = 2; // cgeneral } if (color_channels == ColorWriteAttrib::C_off) { color_write_state = 3; // coff } int alpha_test_state = 0; // anone const AlphaTestAttrib *target_alpha_test = DCAST(AlphaTestAttrib, _target_rs->get_attrib_def(AlphaTestAttrib::get_class_slot())); switch (target_alpha_test->get_mode()) { case AlphaTestAttrib::M_none: case AlphaTestAttrib::M_never: case AlphaTestAttrib::M_always: case AlphaTestAttrib::M_equal: case AlphaTestAttrib::M_not_equal: alpha_test_state = 0; // anone break; case AlphaTestAttrib::M_less: case AlphaTestAttrib::M_less_equal: alpha_test_state = 1; // aless _c->zb->reference_alpha = (int)(target_alpha_test->get_reference_alpha() * ZB_POINT_ALPHA_MAX); break; case AlphaTestAttrib::M_greater: case AlphaTestAttrib::M_greater_equal: alpha_test_state = 2; // amore _c->zb->reference_alpha = (int)(target_alpha_test->get_reference_alpha() * ZB_POINT_ALPHA_MAX); break; } int depth_test_state = 1; // zless _c->depth_test = 1; // set this for ZB_line const DepthTestAttrib *target_depth_test = DCAST(DepthTestAttrib, _target_rs->get_attrib_def(DepthTestAttrib::get_class_slot())); if (target_depth_test->get_mode() == DepthTestAttrib::M_none) { depth_test_state = 0; // zless _c->depth_test = 0; } const ShadeModelAttrib *target_shade_model = DCAST(ShadeModelAttrib, _target_rs->get_attrib_def(ShadeModelAttrib::get_class_slot())); ShadeModelAttrib::Mode shade_model = target_shade_model->get_mode(); if (!needs_normal && !needs_color) { // With no per-vertex lighting, and no per-vertex colors, we might as well // use the flat shading model. shade_model = ShadeModelAttrib::M_flat; } int shade_model_state = 2; // smooth _c->smooth_shade_model = true; if (shade_model == ShadeModelAttrib::M_flat) { _c->smooth_shade_model = false; shade_model_state = 1; // flat if (_c->current_color.v[0] == 1.0f && _c->current_color.v[1] == 1.0f && _c->current_color.v[2] == 1.0f && _c->current_color.v[3] == 1.0f) { shade_model_state = 0; // white } } int texturing_state = _texturing_state; int texfilter_state = 0; // tnearest if (texturing_state > 0) { texfilter_state = _texfilter_state; if (texturing_state < 3 && (_c->matrix_model_projection_no_w_transform || _filled_flat)) { // Don't bother with the perspective-correct algorithm if we're under an // orthonormal lens, e.g. render2d; or if RenderMode::M_filled_flat is // in effect. texturing_state = 1; // textured (not perspective correct) } if (_texture_replace) { // If we're completely replacing the underlying color, then it doesn't // matter what the color is. shade_model_state = 0; } } _c->zb_fill_tri = fill_tri_funcs[depth_write_state][color_write_state][alpha_test_state][depth_test_state][texfilter_state][shade_model_state][texturing_state]; #ifdef DO_PSTATS pixel_count_white_untextured = 0; pixel_count_flat_untextured = 0; pixel_count_smooth_untextured = 0; pixel_count_white_textured = 0; pixel_count_flat_textured = 0; pixel_count_smooth_textured = 0; pixel_count_white_perspective = 0; pixel_count_flat_perspective = 0; pixel_count_smooth_perspective = 0; pixel_count_smooth_multitex2 = 0; pixel_count_smooth_multitex3 = 0; #endif // DO_PSTATS return true; } /** * Draws a series of disconnected triangles. */ bool TinyGraphicsStateGuardian:: draw_triangles(const GeomPrimitivePipelineReader *reader, bool force) { PStatTimer timer(_draw_primitive_pcollector, reader->get_current_thread()); #ifndef NDEBUG if (tinydisplay_cat.is_spam()) { tinydisplay_cat.spam() << "draw_triangles: " << *(reader->get_object()) << "\n"; } #endif // NDEBUG int num_vertices = reader->get_num_vertices(); _vertices_tri_pcollector.add_level(num_vertices); if (reader->is_indexed()) { switch (reader->get_index_type()) { case Geom::NT_uint8: { uint8_t *index = (uint8_t *)reader->get_read_pointer(force); if (index == NULL) { return false; } for (int i = 0; i < num_vertices; i += 3) { GLVertex *v0 = &_vertices[index[i] - _min_vertex]; GLVertex *v1 = &_vertices[index[i + 1] - _min_vertex]; GLVertex *v2 = &_vertices[index[i + 2] - _min_vertex]; gl_draw_triangle(_c, v0, v1, v2); } } break; case Geom::NT_uint16: { uint16_t *index = (uint16_t *)reader->get_read_pointer(force); if (index == NULL) { return false; } for (int i = 0; i < num_vertices; i += 3) { GLVertex *v0 = &_vertices[index[i] - _min_vertex]; GLVertex *v1 = &_vertices[index[i + 1] - _min_vertex]; GLVertex *v2 = &_vertices[index[i + 2] - _min_vertex]; gl_draw_triangle(_c, v0, v1, v2); } } break; case Geom::NT_uint32: { uint32_t *index = (uint32_t *)reader->get_read_pointer(force); if (index == NULL) { return false; } for (int i = 0; i < num_vertices; i += 3) { GLVertex *v0 = &_vertices[index[i] - _min_vertex]; GLVertex *v1 = &_vertices[index[i + 1] - _min_vertex]; GLVertex *v2 = &_vertices[index[i + 2] - _min_vertex]; gl_draw_triangle(_c, v0, v1, v2); } } break; default: tinydisplay_cat.error() << "Invalid index type " << reader->get_index_type() << "!\n"; return false; } } else { int delta = reader->get_first_vertex() - _min_vertex; for (int vi = 0; vi < num_vertices; vi += 3) { GLVertex *v0 = &_vertices[vi + delta]; GLVertex *v1 = &_vertices[vi + delta + 1]; GLVertex *v2 = &_vertices[vi + delta + 2]; gl_draw_triangle(_c, v0, v1, v2); } } return true; } /** * Draws a series of triangle strips. */ bool TinyGraphicsStateGuardian:: draw_tristrips(const GeomPrimitivePipelineReader *reader, bool force) { PStatTimer timer(_draw_primitive_pcollector, reader->get_current_thread()); #ifndef NDEBUG if (tinydisplay_cat.is_spam()) { tinydisplay_cat.spam() << "draw_tristrips: " << *(reader->get_object()) << "\n"; } #endif // NDEBUG // Send the individual triangle strips, stepping over the degenerate // vertices. CPTA_int ends = reader->get_ends(); _primitive_batches_tristrip_pcollector.add_level(ends.size()); if (reader->is_indexed()) { unsigned int start = 0; for (size_t i = 0; i < ends.size(); i++) { _vertices_tristrip_pcollector.add_level(ends[i] - start); int end = ends[i]; nassertr(end - start >= 3, false); switch (reader->get_index_type()) { case Geom::NT_uint8: { uint8_t *index = (uint8_t *)reader->get_read_pointer(force); if (index == NULL) { return false; } GLVertex *v0 = &_vertices[index[start] - _min_vertex]; GLVertex *v1 = &_vertices[index[start + 1] - _min_vertex]; bool reversed = false; for (int vi = start + 2; vi < end; ++vi) { GLVertex *v2 = &_vertices[index[vi] - _min_vertex]; if (reversed) { gl_draw_triangle(_c, v1, v0, v2); reversed = false; } else { gl_draw_triangle(_c, v0, v1, v2); reversed = true; } v0 = v1; v1 = v2; } } break; case Geom::NT_uint16: { uint16_t *index = (uint16_t *)reader->get_read_pointer(force); if (index == NULL) { return false; } GLVertex *v0 = &_vertices[index[start] - _min_vertex]; GLVertex *v1 = &_vertices[index[start + 1] - _min_vertex]; bool reversed = false; for (int vi = start + 2; vi < end; ++vi) { GLVertex *v2 = &_vertices[index[vi] - _min_vertex]; if (reversed) { gl_draw_triangle(_c, v1, v0, v2); reversed = false; } else { gl_draw_triangle(_c, v0, v1, v2); reversed = true; } v0 = v1; v1 = v2; } } break; case Geom::NT_uint32: { uint32_t *index = (uint32_t *)reader->get_read_pointer(force); if (index == NULL) { return false; } GLVertex *v0 = &_vertices[index[start] - _min_vertex]; GLVertex *v1 = &_vertices[index[start + 1] - _min_vertex]; bool reversed = false; for (int vi = start + 2; vi < end; ++vi) { GLVertex *v2 = &_vertices[index[vi] - _min_vertex]; if (reversed) { gl_draw_triangle(_c, v1, v0, v2); reversed = false; } else { gl_draw_triangle(_c, v0, v1, v2); reversed = true; } v0 = v1; v1 = v2; } } break; default: tinydisplay_cat.error() << "Invalid index type " << reader->get_index_type() << "!\n"; return false; } start = ends[i] + 2; } } else { unsigned int start = 0; int delta = reader->get_first_vertex() - _min_vertex; for (size_t i = 0; i < ends.size(); i++) { _vertices_tristrip_pcollector.add_level(ends[i] - start); int end = ends[i]; nassertr(end - start >= 3, false); GLVertex *v0 = &_vertices[start + delta]; GLVertex *v1 = &_vertices[start + delta + 1]; bool reversed = false; for (int vi = start + 2; vi < end; ++vi) { GLVertex *v2 = &_vertices[vi + delta]; if (reversed) { gl_draw_triangle(_c, v1, v0, v2); reversed = false; } else { gl_draw_triangle(_c, v0, v1, v2); reversed = true; } v0 = v1; v1 = v2; } start = ends[i] + 2; } } return true; } /** * Draws a series of disconnected line segments. */ bool TinyGraphicsStateGuardian:: draw_lines(const GeomPrimitivePipelineReader *reader, bool force) { PStatTimer timer(_draw_primitive_pcollector, reader->get_current_thread()); #ifndef NDEBUG if (tinydisplay_cat.is_spam()) { tinydisplay_cat.spam() << "draw_lines: " << *(reader->get_object()) << "\n"; } #endif // NDEBUG int num_vertices = reader->get_num_vertices(); _vertices_other_pcollector.add_level(num_vertices); if (reader->is_indexed()) { switch (reader->get_index_type()) { case Geom::NT_uint8: { uint8_t *index = (uint8_t *)reader->get_read_pointer(force); if (index == NULL) { return false; } for (int i = 0; i < num_vertices; i += 2) { GLVertex *v0 = &_vertices[index[i] - _min_vertex]; GLVertex *v1 = &_vertices[index[i + 1] - _min_vertex]; gl_draw_line(_c, v0, v1); } } break; case Geom::NT_uint16: { uint16_t *index = (uint16_t *)reader->get_read_pointer(force); if (index == NULL) { return false; } for (int i = 0; i < num_vertices; i += 2) { GLVertex *v0 = &_vertices[index[i] - _min_vertex]; GLVertex *v1 = &_vertices[index[i + 1] - _min_vertex]; gl_draw_line(_c, v0, v1); } } break; case Geom::NT_uint32: { uint32_t *index = (uint32_t *)reader->get_read_pointer(force); if (index == NULL) { return false; } for (int i = 0; i < num_vertices; i += 2) { GLVertex *v0 = &_vertices[index[i] - _min_vertex]; GLVertex *v1 = &_vertices[index[i + 1] - _min_vertex]; gl_draw_line(_c, v0, v1); } } break; default: tinydisplay_cat.error() << "Invalid index type " << reader->get_index_type() << "!\n"; return false; } } else { int delta = reader->get_first_vertex() - _min_vertex; for (int vi = 0; vi < num_vertices; vi += 2) { GLVertex *v0 = &_vertices[vi + delta]; GLVertex *v1 = &_vertices[vi + delta + 1]; gl_draw_line(_c, v0, v1); } } return true; } /** * Draws a series of disconnected points. */ bool TinyGraphicsStateGuardian:: draw_points(const GeomPrimitivePipelineReader *reader, bool force) { PStatTimer timer(_draw_primitive_pcollector, reader->get_current_thread()); #ifndef NDEBUG if (tinydisplay_cat.is_spam()) { tinydisplay_cat.spam() << "draw_points: " << *(reader->get_object()) << "\n"; } #endif // NDEBUG int num_vertices = reader->get_num_vertices(); _vertices_other_pcollector.add_level(num_vertices); if (reader->is_indexed()) { switch (reader->get_index_type()) { case Geom::NT_uint8: { uint8_t *index = (uint8_t *)reader->get_read_pointer(force); if (index == NULL) { return false; } for (int i = 0; i < num_vertices; ++i) { GLVertex *v0 = &_vertices[index[i] - _min_vertex]; gl_draw_point(_c, v0); } } break; case Geom::NT_uint16: { uint16_t *index = (uint16_t *)reader->get_read_pointer(force); if (index == NULL) { return false; } for (int i = 0; i < num_vertices; ++i) { GLVertex *v0 = &_vertices[index[i] - _min_vertex]; gl_draw_point(_c, v0); } } break; case Geom::NT_uint32: { uint32_t *index = (uint32_t *)reader->get_read_pointer(force); if (index == NULL) { return false; } for (int i = 0; i < num_vertices; ++i) { GLVertex *v0 = &_vertices[index[i] - _min_vertex]; gl_draw_point(_c, v0); } } break; default: tinydisplay_cat.error() << "Invalid index type " << reader->get_index_type() << "!\n"; return false; } } else { int delta = reader->get_first_vertex() - _min_vertex; for (int vi = 0; vi < num_vertices; ++vi) { GLVertex *v0 = &_vertices[vi + delta]; gl_draw_point(_c, v0); } } return true; } /** * Called after a sequence of draw_primitive() functions are called, this * should do whatever cleanup is appropriate. */ void TinyGraphicsStateGuardian:: end_draw_primitives() { #ifdef DO_PSTATS _pixel_count_white_untextured_pcollector.add_level(pixel_count_white_untextured); _pixel_count_flat_untextured_pcollector.add_level(pixel_count_flat_untextured); _pixel_count_smooth_untextured_pcollector.add_level(pixel_count_smooth_untextured); _pixel_count_white_textured_pcollector.add_level(pixel_count_white_textured); _pixel_count_flat_textured_pcollector.add_level(pixel_count_flat_textured); _pixel_count_smooth_textured_pcollector.add_level(pixel_count_smooth_textured); _pixel_count_white_perspective_pcollector.add_level(pixel_count_white_perspective); _pixel_count_flat_perspective_pcollector.add_level(pixel_count_flat_perspective); _pixel_count_smooth_perspective_pcollector.add_level(pixel_count_smooth_perspective); _pixel_count_smooth_multitex2_pcollector.add_level(pixel_count_smooth_multitex2); _pixel_count_smooth_multitex3_pcollector.add_level(pixel_count_smooth_multitex3); #endif // DO_PSTATS GraphicsStateGuardian::end_draw_primitives(); } /** * 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 TinyGraphicsStateGuardian:: framebuffer_copy_to_texture(Texture *tex, int view, int z, const DisplayRegion *dr, const RenderBuffer &rb) { nassertr(tex != NULL && dr != NULL, false); int xo, yo, w, h; dr->get_region_pixels_i(xo, yo, w, h); tex->setup_2d_texture(w, h, Texture::T_unsigned_byte, Texture::F_rgba); TextureContext *tc = tex->prepare_now(view, get_prepared_objects(), this); nassertr(tc != (TextureContext *)NULL, false); TinyTextureContext *gtc = DCAST(TinyTextureContext, tc); GLTexture *gltex = >c->_gltex; if (!setup_gltex(gltex, tex->get_x_size(), tex->get_y_size(), 1)) { return false; } LColor border_color = tex->get_border_color(); border_color = border_color.fmin(LColor(1, 1, 1, 1)).fmax(LColor::zero()); gltex->border_color.v[0] = border_color[0]; gltex->border_color.v[1] = border_color[1]; gltex->border_color.v[2] = border_color[2]; gltex->border_color.v[3] = border_color[3]; PIXEL *ip = gltex->levels[0].pixmap + gltex->xsize * gltex->ysize; PIXEL *fo = _c->zb->pbuf + xo + yo * _c->zb->linesize / PSZB; for (int y = 0; y < gltex->ysize; ++y) { ip -= gltex->xsize; memcpy(ip, fo, gltex->xsize * PSZB); fo += _c->zb->linesize / PSZB; } gtc->update_data_size_bytes(gltex->xsize * gltex->ysize * 4); gtc->mark_loaded(); gtc->enqueue_lru(&_prepared_objects->_graphics_memory_lru); return true; } /** * 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 TinyGraphicsStateGuardian:: framebuffer_copy_to_ram(Texture *tex, int view, int z, const DisplayRegion *dr, const RenderBuffer &rb) { nassertr(tex != NULL && dr != NULL, false); int xo, yo, w, h; dr->get_region_pixels_i(xo, yo, w, h); Texture::TextureType texture_type; int z_size; if (z >= 0) { texture_type = Texture::TT_cube_map; z_size = 6; } else { texture_type = Texture::TT_2d_texture; z_size = 1; } Texture::ComponentType component_type = Texture::T_unsigned_byte; Texture::Format format = Texture::F_rgba; if (tex->get_x_size() != w || tex->get_y_size() != h || tex->get_z_size() != z_size || tex->get_component_type() != component_type || tex->get_format() != format || tex->get_texture_type() != texture_type) { // Re-setup the texture; its properties have changed. tex->setup_texture(texture_type, w, h, z_size, component_type, format); } nassertr(z < tex->get_z_size(), false); unsigned char *image_ptr = tex->modify_ram_image(); size_t image_size = tex->get_ram_image_size(); if (z >= 0 || view > 0) { image_size = tex->get_expected_ram_page_size(); if (z >= 0) { image_ptr += z * image_size; } if (view > 0) { image_ptr += (view * tex->get_z_size()) * image_size; } } PIXEL *ip = (PIXEL *)(image_ptr + image_size); PIXEL *fo = _c->zb->pbuf + xo + yo * _c->zb->linesize / PSZB; for (int y = 0; y < h; ++y) { ip -= w; #ifndef WORDS_BIGENDIAN // On a little-endian machine, we can copy the whole row at a time. memcpy(ip, fo, w * PSZB); #else // On a big-endian machine, we have to reverse the color-component order. const char *source = (const char *)fo; const char *stop = (const char *)fo + w * PSZB; char *dest = (char *)ip; while (source < stop) { char b = source[0]; char g = source[1]; char r = source[2]; char a = source[3]; dest[0] = a; dest[1] = r; dest[2] = g; dest[3] = b; dest += 4; source += 4; } #endif fo += _c->zb->linesize / PSZB; } return 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 TinyGraphicsStateGuardian:: set_state_and_transform(const RenderState *target, const TransformState *transform) { #ifndef NDEBUG if (tinydisplay_cat.is_spam()) { tinydisplay_cat.spam() << "Setting GSG state to " << (void *)target << ":\n"; target->write(tinydisplay_cat.spam(false), 2); transform->write(tinydisplay_cat.spam(false), 2); } #endif _state_pcollector.add_level(1); PStatTimer timer1(_draw_set_state_pcollector); if (transform != _internal_transform) { PStatTimer timer(_draw_set_state_transform_pcollector); _state_pcollector.add_level(1); _internal_transform = transform; do_issue_transform(); } if (target == _state_rs && (_state_mask | _inv_state_mask).is_all_on()) { return; } _target_rs = target; int color_slot = ColorAttrib::get_class_slot(); int color_scale_slot = ColorScaleAttrib::get_class_slot(); if (_target_rs->get_attrib(color_slot) != _state_rs->get_attrib(color_slot) || _target_rs->get_attrib(color_scale_slot) != _state_rs->get_attrib(color_scale_slot) || !_state_mask.get_bit(color_slot) || !_state_mask.get_bit(color_scale_slot)) { PStatTimer timer(_draw_set_state_color_pcollector); do_issue_color(); do_issue_color_scale(); _state_mask.set_bit(color_slot); _state_mask.set_bit(color_scale_slot); } int cull_face_slot = CullFaceAttrib::get_class_slot(); if (_target_rs->get_attrib(cull_face_slot) != _state_rs->get_attrib(cull_face_slot) || !_state_mask.get_bit(cull_face_slot)) { PStatTimer timer(_draw_set_state_cull_face_pcollector); do_issue_cull_face(); _state_mask.set_bit(cull_face_slot); } int depth_offset_slot = DepthOffsetAttrib::get_class_slot(); if (_target_rs->get_attrib(depth_offset_slot) != _state_rs->get_attrib(depth_offset_slot) || !_state_mask.get_bit(depth_offset_slot)) { // PStatTimer timer(_draw_set_state_depth_offset_pcollector); do_issue_depth_offset(); _state_mask.set_bit(depth_offset_slot); } int rescale_normal_slot = RescaleNormalAttrib::get_class_slot(); if (_target_rs->get_attrib(rescale_normal_slot) != _state_rs->get_attrib(rescale_normal_slot) || !_state_mask.get_bit(rescale_normal_slot)) { PStatTimer timer(_draw_set_state_rescale_normal_pcollector); do_issue_rescale_normal(); _state_mask.set_bit(rescale_normal_slot); } int render_mode_slot = RenderModeAttrib::get_class_slot(); if (_target_rs->get_attrib(render_mode_slot) != _state_rs->get_attrib(render_mode_slot) || !_state_mask.get_bit(render_mode_slot)) { PStatTimer timer(_draw_set_state_render_mode_pcollector); do_issue_render_mode(); _state_mask.set_bit(render_mode_slot); } int texture_slot = TextureAttrib::get_class_slot(); if (_target_rs->get_attrib(texture_slot) != _state_rs->get_attrib(texture_slot) || !_state_mask.get_bit(texture_slot)) { PStatTimer timer(_draw_set_state_texture_pcollector); determine_target_texture(); do_issue_texture(); _state_mask.set_bit(texture_slot); } int material_slot = MaterialAttrib::get_class_slot(); if (_target_rs->get_attrib(material_slot) != _state_rs->get_attrib(material_slot) || !_state_mask.get_bit(material_slot)) { PStatTimer timer(_draw_set_state_material_pcollector); do_issue_material(); _state_mask.set_bit(material_slot); } int light_slot = LightAttrib::get_class_slot(); if (_target_rs->get_attrib(light_slot) != _state_rs->get_attrib(light_slot) || !_state_mask.get_bit(light_slot)) { PStatTimer timer(_draw_set_state_light_pcollector); do_issue_light(); _state_mask.set_bit(light_slot); } int scissor_slot = ScissorAttrib::get_class_slot(); if (_target_rs->get_attrib(scissor_slot) != _state_rs->get_attrib(scissor_slot) || !_state_mask.get_bit(scissor_slot)) { PStatTimer timer(_draw_set_state_scissor_pcollector); do_issue_scissor(); _state_mask.set_bit(scissor_slot); } _state_rs = _target_rs; } /** * 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 *TinyGraphicsStateGuardian:: prepare_texture(Texture *tex, int view) { switch (tex->get_texture_type()) { case Texture::TT_1d_texture: case Texture::TT_2d_texture: // These are supported. break; default: // Anything else is not supported. tinydisplay_cat.info() << "Not loading texture " << tex->get_name() << ": " << tex->get_texture_type() << "\n"; return NULL; } // Even though the texture might be compressed now, it might have an // available uncompressed version that we can load. So don't reject it out- // of-hand just because it's compressed. /* if (tex->get_ram_image_compression() != Texture::CM_off) { tinydisplay_cat.info() << "Not loading texture " << tex->get_name() << ": " << tex->get_ram_image_compression() << "\n"; return NULL; } */ TinyTextureContext *gtc = new TinyTextureContext(_prepared_objects, tex, view); return gtc; } /** * 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 TinyGraphicsStateGuardian:: update_texture(TextureContext *tc, bool force) { apply_texture(tc); TinyTextureContext *gtc = DCAST(TinyTextureContext, tc); GLTexture *gltex = >c->_gltex; if (gtc->was_image_modified() || gltex->num_levels == 0) { // If the texture image was modified, reload the texture. Texture *tex = gtc->get_texture(); bool okflag = upload_texture(gtc, force, tex->uses_mipmaps()); if (!okflag) { tinydisplay_cat.error() << "Could not load " << *tex << "\n"; return false; } } gtc->enqueue_lru(&_prepared_objects->_graphics_memory_lru); return true; } /** * 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 TinyGraphicsStateGuardian:: update_texture(TextureContext *tc, bool force, int stage_index, bool uses_mipmaps) { if (!update_texture(tc, force)) { return false; } TinyTextureContext *gtc = DCAST(TinyTextureContext, tc); GLTexture *gltex = >c->_gltex; if (uses_mipmaps && gltex->num_levels <= 1) { // We don't have mipmaps, yet we are sampling with mipmaps. Texture *tex = gtc->get_texture(); bool okflag = upload_texture(gtc, force, true); if (!okflag) { tinydisplay_cat.error() << "Could not load " << *tex << "\n"; return false; } } _c->current_textures[stage_index] = gltex; ZTextureDef *texture_def = &_c->zb->current_textures[stage_index]; texture_def->levels = gltex->levels; texture_def->s_max = gltex->s_max; texture_def->t_max = gltex->t_max; const V4 &bc = gltex->border_color; int r = (int)(bc.v[0] * (ZB_POINT_RED_MAX - ZB_POINT_RED_MIN) + ZB_POINT_RED_MIN); int g = (int)(bc.v[1] * (ZB_POINT_GREEN_MAX - ZB_POINT_GREEN_MIN) + ZB_POINT_GREEN_MIN); int b = (int)(bc.v[2] * (ZB_POINT_BLUE_MAX - ZB_POINT_BLUE_MIN) + ZB_POINT_BLUE_MIN); int a = (int)(bc.v[3] * (ZB_POINT_ALPHA_MAX - ZB_POINT_ALPHA_MIN) + ZB_POINT_ALPHA_MIN); texture_def->border_color = RGBA_TO_PIXEL(r, g, b, a); return true; } /** * Frees the GL resources previously allocated for the texture. This function * should never be called directly; instead, call Texture::release() (or * simply let the Texture destruct). */ void TinyGraphicsStateGuardian:: release_texture(TextureContext *tc) { TinyTextureContext *gtc = DCAST(TinyTextureContext, tc); _texturing_state = 0; // just in case GLTexture *gltex = >c->_gltex; if (gltex->allocated_buffer != NULL) { nassertv(gltex->num_levels != 0); TinyTextureContext::get_class_type().dec_memory_usage(TypeHandle::MC_array, gltex->total_bytecount); PANDA_FREE_ARRAY(gltex->allocated_buffer); gltex->allocated_buffer = NULL; gltex->total_bytecount = 0; gltex->num_levels = 0; } else { nassertv(gltex->num_levels == 0); } gtc->dequeue_lru(); delete gtc; } /** * */ void TinyGraphicsStateGuardian:: 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 num_enabled = 0; int num_on_lights = 0; const LightAttrib *target_light = DCAST(LightAttrib, _target_rs->get_attrib_def(LightAttrib::get_class_slot())); if (display_cat.is_spam()) { display_cat.spam() << "do_issue_light: " << target_light << "\n"; } // First, release all of the previously-assigned lights. clear_light_state(); // Now, assign new lights. 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_enabled = true; _c->lighting_enabled = true; if (light_obj->get_type() == AmbientLight::get_class_type()) { // Accumulate all of the ambient lights together into one. cur_ambient_light += light_obj->get_color(); } else { // Other kinds of lights each get their own GLLight object. light_obj->bind(this, light, num_enabled); num_enabled++; // Handle the diffuse color here, since all lights have this property. GLLight *gl_light = _c->first_light; nassertv(gl_light != NULL); const LColor &diffuse = light_obj->get_color(); gl_light->diffuse.v[0] = diffuse[0]; gl_light->diffuse.v[1] = diffuse[1]; gl_light->diffuse.v[2] = diffuse[2]; gl_light->diffuse.v[3] = diffuse[3]; } } } _c->ambient_light_model.v[0] = cur_ambient_light[0]; _c->ambient_light_model.v[1] = cur_ambient_light[1]; _c->ambient_light_model.v[2] = cur_ambient_light[2]; _c->ambient_light_model.v[3] = cur_ambient_light[3]; // Changing the lighting state means we need to reapply the transform in // begin_draw_primitives(). _transform_stale = true; } /** * 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 TinyGraphicsStateGuardian:: bind_light(PointLight *light_obj, const NodePath &light, int light_id) { pair lookup = _plights.insert(Lights::value_type(light, GLLight())); GLLight *gl_light = &(*lookup.first).second; if (lookup.second) { // It's a brand new light. Define it. memset(gl_light, 0, sizeof(GLLight)); const LColor &specular = light_obj->get_specular_color(); gl_light->specular.v[0] = specular[0]; gl_light->specular.v[1] = specular[1]; gl_light->specular.v[2] = specular[2]; gl_light->specular.v[3] = specular[3]; // Position needs to specify x, y, z, and w w == 1 implies non-infinite // position CPT(TransformState) render_transform = _cs_transform->compose(_scene_setup->get_world_transform()); CPT(TransformState) transform = light.get_transform(_scene_setup->get_scene_root().get_parent()); CPT(TransformState) net_transform = render_transform->compose(transform); LPoint3 pos = light_obj->get_point() * net_transform->get_mat(); gl_light->position.v[0] = pos[0]; gl_light->position.v[1] = pos[1]; gl_light->position.v[2] = pos[2]; gl_light->position.v[3] = 1.0f; // Exponent == 0 implies uniform light distribution gl_light->spot_exponent = 0.0f; // Cutoff == 180 means uniform point light source gl_light->spot_cutoff = 180.0f; const LVecBase3 &att = light_obj->get_attenuation(); gl_light->attenuation[0] = att[0]; gl_light->attenuation[1] = att[1]; gl_light->attenuation[2] = att[2]; } nassertv(gl_light->next == NULL); // Add it to the linked list of active lights. gl_light->next = _c->first_light; _c->first_light = gl_light; } /** * 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 TinyGraphicsStateGuardian:: bind_light(DirectionalLight *light_obj, const NodePath &light, int light_id) { pair lookup = _dlights.insert(Lights::value_type(light, GLLight())); GLLight *gl_light = &(*lookup.first).second; if (lookup.second) { // It's a brand new light. Define it. memset(gl_light, 0, sizeof(GLLight)); const LColor &specular = light_obj->get_specular_color(); gl_light->specular.v[0] = specular[0]; gl_light->specular.v[1] = specular[1]; gl_light->specular.v[2] = specular[2]; gl_light->specular.v[3] = specular[3]; // Position needs to specify x, y, z, and w w == 0 implies light is at // infinity CPT(TransformState) render_transform = _cs_transform->compose(_scene_setup->get_world_transform()); CPT(TransformState) transform = light.get_transform(_scene_setup->get_scene_root().get_parent()); CPT(TransformState) net_transform = render_transform->compose(transform); LVector3 dir = light_obj->get_direction() * net_transform->get_mat(); dir.normalize(); gl_light->position.v[0] = -dir[0]; gl_light->position.v[1] = -dir[1]; gl_light->position.v[2] = -dir[2]; gl_light->position.v[3] = 0.0f; gl_light->norm_position.v[0] = -dir[0]; gl_light->norm_position.v[1] = -dir[1]; gl_light->norm_position.v[2] = -dir[2]; gl_V3_Norm(&gl_light->norm_position); // Exponent == 0 implies uniform light distribution gl_light->spot_exponent = 0.0f; // Cutoff == 180 means uniform point light source gl_light->spot_cutoff = 180.0f; // Default attenuation values (only spotlight and point light can modify // these) gl_light->attenuation[0] = 1.0f; gl_light->attenuation[1] = 0.0f; gl_light->attenuation[2] = 0.0f; } nassertv(gl_light->next == NULL); // Add it to the linked list of active lights. gl_light->next = _c->first_light; _c->first_light = gl_light; } /** * 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 TinyGraphicsStateGuardian:: bind_light(Spotlight *light_obj, const NodePath &light, int light_id) { pair lookup = _plights.insert(Lights::value_type(light, GLLight())); GLLight *gl_light = &(*lookup.first).second; if (lookup.second) { // It's a brand new light. Define it. memset(gl_light, 0, sizeof(GLLight)); const LColor &specular = light_obj->get_specular_color(); gl_light->specular.v[0] = specular[0]; gl_light->specular.v[1] = specular[1]; gl_light->specular.v[2] = specular[2]; gl_light->specular.v[3] = specular[3]; Lens *lens = light_obj->get_lens(); nassertv(lens != (Lens *)NULL); // Position needs to specify x, y, z, and w w == 1 implies non-infinite // position CPT(TransformState) render_transform = _cs_transform->compose(_scene_setup->get_world_transform()); CPT(TransformState) transform = light.get_transform(_scene_setup->get_scene_root().get_parent()); CPT(TransformState) net_transform = render_transform->compose(transform); const LMatrix4 &light_mat = net_transform->get_mat(); LPoint3 pos = lens->get_nodal_point() * light_mat; LVector3 dir = lens->get_view_vector() * light_mat; dir.normalize(); gl_light->position.v[0] = pos[0]; gl_light->position.v[1] = pos[1]; gl_light->position.v[2] = pos[2]; gl_light->position.v[3] = 1.0f; gl_light->spot_direction.v[0] = dir[0]; gl_light->spot_direction.v[1] = dir[1]; gl_light->spot_direction.v[2] = dir[2]; gl_light->norm_spot_direction.v[0] = dir[0]; gl_light->norm_spot_direction.v[1] = dir[1]; gl_light->norm_spot_direction.v[2] = dir[2]; gl_V3_Norm(&gl_light->norm_spot_direction); gl_light->spot_exponent = light_obj->get_exponent(); gl_light->spot_cutoff = lens->get_hfov() * 0.5f; const LVecBase3 &att = light_obj->get_attenuation(); gl_light->attenuation[0] = att[0]; gl_light->attenuation[1] = att[1]; gl_light->attenuation[2] = att[2]; } nassertv(gl_light->next == NULL); // Add it to the linked list of active lights. gl_light->next = _c->first_light; _c->first_light = gl_light; } /** * Sends the indicated transform matrix to the graphics API to be applied to * future vertices. * * This transform is the internal_transform, already converted into the GSG's * internal coordinate system. */ void TinyGraphicsStateGuardian:: do_issue_transform() { _transform_state_pcollector.add_level(1); _transform_stale = true; if (_auto_rescale_normal) { do_auto_rescale_normal(); } } /** * */ void TinyGraphicsStateGuardian:: do_issue_render_mode() { const RenderModeAttrib *target_render_mode = DCAST(RenderModeAttrib, _target_rs->get_attrib_def(RenderModeAttrib::get_class_slot())); _filled_flat = false; switch (target_render_mode->get_mode()) { case RenderModeAttrib::M_unchanged: case RenderModeAttrib::M_filled: _c->draw_triangle_front = gl_draw_triangle_fill; _c->draw_triangle_back = gl_draw_triangle_fill; break; case RenderModeAttrib::M_filled_flat: _c->draw_triangle_front = gl_draw_triangle_fill; _c->draw_triangle_back = gl_draw_triangle_fill; _filled_flat = true; break; case RenderModeAttrib::M_wireframe: _c->draw_triangle_front = gl_draw_triangle_line; _c->draw_triangle_back = gl_draw_triangle_line; break; case RenderModeAttrib::M_point: _c->draw_triangle_front = gl_draw_triangle_point; _c->draw_triangle_back = gl_draw_triangle_point; break; default: tinydisplay_cat.error() << "Unknown render mode " << (int)target_render_mode->get_mode() << endl; } } /** * */ void TinyGraphicsStateGuardian:: do_issue_rescale_normal() { const RescaleNormalAttrib *target_rescale_normal = DCAST(RescaleNormalAttrib, _target_rs->get_attrib_def(RescaleNormalAttrib::get_class_slot())); RescaleNormalAttrib::Mode mode = target_rescale_normal->get_mode(); _auto_rescale_normal = false; switch (mode) { case RescaleNormalAttrib::M_none: _c->normalize_enabled = false; _c->normal_scale = 1.0f; break; case RescaleNormalAttrib::M_normalize: _c->normalize_enabled = true; _c->normal_scale = 1.0f; break; case RescaleNormalAttrib::M_rescale: case RescaleNormalAttrib::M_auto: _auto_rescale_normal = true; do_auto_rescale_normal(); break; default: tinydisplay_cat.error() << "Unknown rescale_normal mode " << (int)mode << endl; } } /** * */ void TinyGraphicsStateGuardian:: do_issue_depth_offset() { const DepthOffsetAttrib *target_depth_offset = DCAST(DepthOffsetAttrib, _target_rs->get_attrib_def(DepthOffsetAttrib::get_class_slot())); int offset = target_depth_offset->get_offset(); _c->zbias = offset; PN_stdfloat min_value = target_depth_offset->get_min_value(); PN_stdfloat max_value = target_depth_offset->get_max_value(); if (min_value == 0.0f && max_value == 1.0f) { _c->has_zrange = false; } else { _c->has_zrange = true; _c->zmin = min_value; _c->zrange = max_value - min_value; } } /** * */ void TinyGraphicsStateGuardian:: do_issue_cull_face() { const CullFaceAttrib *target_cull_face = DCAST(CullFaceAttrib, _target_rs->get_attrib_def(CullFaceAttrib::get_class_slot())); CullFaceAttrib::Mode mode = target_cull_face->get_effective_mode(); switch (mode) { case CullFaceAttrib::M_cull_none: _c->cull_face_enabled = false; break; case CullFaceAttrib::M_cull_clockwise: _c->cull_face_enabled = true; _c->cull_clockwise = true; break; case CullFaceAttrib::M_cull_counter_clockwise: _c->cull_face_enabled = true; _c->cull_clockwise = false; break; default: tinydisplay_cat.error() << "invalid cull face mode " << (int)mode << endl; break; } } /** * */ void TinyGraphicsStateGuardian:: do_issue_material() { static Material empty; const MaterialAttrib *target_material = DCAST(MaterialAttrib, _target_rs->get_attrib_def(MaterialAttrib::get_class_slot())); const Material *material; if (target_material == (MaterialAttrib *)NULL || target_material->is_off()) { material = ∅ } else { material = target_material->get_material(); } // Apply the material parameters to the front face. setup_material(&_c->materials[0], material); if (material->get_twoside()) { // Also apply the material parameters to the back face. setup_material(&_c->materials[1], material); } _c->local_light_model = material->get_local(); _c->light_model_two_side = material->get_twoside(); } /** * */ void TinyGraphicsStateGuardian:: do_issue_texture() { _texturing_state = 0; // untextured _c->num_textures_enabled = 0; int num_stages = _target_texture->get_num_on_ff_stages(); if (num_stages == 0) { // No texturing. return; } nassertv(num_stages <= MAX_TEXTURE_STAGES); bool all_replace = true; bool all_nearest = true; bool all_mipmap_nearest = true; bool any_mipmap = false; bool needs_general = false; Texture::QualityLevel best_quality_level = Texture::QL_default; for (int si = 0; si < num_stages; ++si) { TextureStage *stage = _target_texture->get_on_ff_stage(si); Texture *texture = _target_texture->get_on_texture(stage); nassertv(texture != (Texture *)NULL); int view = get_current_tex_view_offset() + stage->get_tex_view_offset(); TextureContext *tc = texture->prepare_now(view, _prepared_objects, this); if (tc == (TextureContext *)NULL) { // Something wrong with this texture; skip it. return; } // Get the sampler state that we are supposed to use. const SamplerState &sampler = _target_texture->get_on_sampler(stage); // Then, turn on the current texture mode. if (!update_texture(tc, false, si, sampler.uses_mipmaps())) { return; } // M_replace means M_replace; anything else is treated the same as // M_modulate. if (stage->get_mode() != TextureStage::M_replace) { all_replace = false; } Texture::QualityLevel quality_level = _texture_quality_override; if (quality_level == Texture::QL_default) { quality_level = texture->get_quality_level(); } if (quality_level == Texture::QL_default) { quality_level = texture_quality_level; } best_quality_level = max(best_quality_level, quality_level); ZTextureDef *texture_def = &_c->zb->current_textures[si]; // Fill in the filter func pointers. These may not actually get called, // if we decide below we can inline the filters. SamplerState::FilterType minfilter = sampler.get_minfilter(); SamplerState::FilterType magfilter = sampler.get_magfilter(); if (td_ignore_mipmaps && SamplerState::is_mipmap(minfilter)) { // Downgrade mipmaps. if (minfilter == SamplerState::FT_nearest_mipmap_nearest) { minfilter = SamplerState::FT_nearest; } else { minfilter = SamplerState::FT_linear; } } // Depending on this particular texture's quality level, we may downgrade // the requested filters. if (quality_level == Texture::QL_fastest) { minfilter = SamplerState::FT_nearest; magfilter = SamplerState::FT_nearest; } else if (quality_level == Texture::QL_normal) { if (SamplerState::is_mipmap(minfilter)) { minfilter = SamplerState::FT_nearest_mipmap_nearest; } else { minfilter = SamplerState::FT_nearest; } magfilter = SamplerState::FT_nearest; } else if (quality_level == Texture::QL_best) { minfilter = sampler.get_effective_minfilter(); magfilter = sampler.get_effective_magfilter(); } texture_def->tex_minfilter_func = get_tex_filter_func(minfilter); texture_def->tex_magfilter_func = get_tex_filter_func(magfilter); SamplerState::WrapMode wrap_u = sampler.get_wrap_u(); SamplerState::WrapMode wrap_v = sampler.get_wrap_v(); if (td_ignore_clamp) { wrap_u = SamplerState::WM_repeat; wrap_v = SamplerState::WM_repeat; } if (wrap_u != SamplerState::WM_repeat || wrap_v != SamplerState::WM_repeat) { // We have some nonstandard wrap mode. This will force the use of the // general texfilter mode. needs_general = true; // We need another level of indirection to implement the different // texcoord wrap modes. This means we will be using the _impl function // pointers, which are called by the toplevel function. texture_def->tex_minfilter_func_impl = texture_def->tex_minfilter_func; texture_def->tex_magfilter_func_impl = texture_def->tex_magfilter_func; // Now assign the toplevel function pointer to do the appropriate // texture coordinate wrappingclamping. texture_def->tex_minfilter_func = apply_wrap_general_minfilter; texture_def->tex_magfilter_func = apply_wrap_general_magfilter; texture_def->tex_wrap_u_func = get_tex_wrap_func(wrap_u); texture_def->tex_wrap_v_func = get_tex_wrap_func(wrap_v); // The following special cases are handled inline, rather than relying // on the above wrap function pointers. if (wrap_u && SamplerState::WM_border_color && wrap_v == SamplerState::WM_border_color) { texture_def->tex_minfilter_func = apply_wrap_border_color_minfilter; texture_def->tex_magfilter_func = apply_wrap_border_color_magfilter; } else if (wrap_u && SamplerState::WM_clamp && wrap_v == SamplerState::WM_clamp) { texture_def->tex_minfilter_func = apply_wrap_clamp_minfilter; texture_def->tex_magfilter_func = apply_wrap_clamp_magfilter; } } if (minfilter != SamplerState::FT_nearest || magfilter != SamplerState::FT_nearest) { all_nearest = false; } if (minfilter != SamplerState::FT_nearest_mipmap_nearest || magfilter != SamplerState::FT_nearest) { all_mipmap_nearest = false; } if (SamplerState::is_mipmap(minfilter)) { any_mipmap = true; } } // Set a few state cache values. _c->num_textures_enabled = num_stages; _texture_replace = all_replace; _texturing_state = 2; // perspective (perspective-correct texturing) if (num_stages >= 3) { _texturing_state = 4; // multitex3 } else if (num_stages == 2) { _texturing_state = 3; // multitex2 } else if (!td_perspective_textures) { _texturing_state = 1; // textured (not perspective correct) } if (best_quality_level == Texture::QL_best) { // This is the most generic texture filter. Slow, but pretty. _texfilter_state = 2; // tgeneral if (!needs_general) { if (all_nearest) { // This case is inlined. _texfilter_state = 0; // tnearest } else if (all_mipmap_nearest) { // So is this case. _texfilter_state = 1; // tmipmap } } } else if (best_quality_level == Texture::QL_fastest) { // This is the cheapest texture filter. We disallow mipmaps and // perspective correctness. _texfilter_state = 0; // tnearest _texturing_state = 1; // textured (not perspective correct, no multitexture) } else { // This is the default texture filter. We use nearest sampling if there // are no mipmaps, and mipmap_nearest if there are any mipmaps--these are // the two inlined filters. _texfilter_state = 0; // tnearest if (any_mipmap) { _texfilter_state = 1; // tmipmap } if (needs_general) { // To support nonstandard texcoord wrapping etc, we need to force the // general texfilter mode. _texfilter_state = 2; // tgeneral } } } /** * */ void TinyGraphicsStateGuardian:: do_issue_scissor() { const ScissorAttrib *target_scissor = DCAST(ScissorAttrib, _target_rs->get_attrib_def(ScissorAttrib::get_class_slot())); const LVecBase4 &frame = target_scissor->get_frame(); set_scissor(frame[0], frame[1], frame[2], frame[3]); } /** * Sets up the scissor region, as a set of coordinates relative to the current * viewport. */ void TinyGraphicsStateGuardian:: set_scissor(PN_stdfloat left, PN_stdfloat right, PN_stdfloat bottom, PN_stdfloat top) { _c->scissor.left = left; _c->scissor.right = right; _c->scissor.bottom = bottom; _c->scissor.top = top; gl_eval_viewport(_c); PN_stdfloat xsize = right - left; PN_stdfloat ysize = top - bottom; PN_stdfloat xcenter = (left + right) - 1.0f; PN_stdfloat ycenter = (bottom + top) - 1.0f; if (xsize == 0.0f || ysize == 0.0f) { // If the scissor region is zero, nothing will be drawn anyway, so don't // worry about it. _scissor_mat = TransformState::make_identity(); } else { _scissor_mat = TransformState::make_scale(LVecBase3(1.0f / xsize, 1.0f / ysize, 1.0f))->compose(TransformState::make_pos(LPoint3(-xcenter, -ycenter, 0.0f))); } } /** * Updates the graphics state with the current information for this texture, * and makes it the current texture available for rendering. */ bool TinyGraphicsStateGuardian:: apply_texture(TextureContext *tc) { TinyTextureContext *gtc = DCAST(TinyTextureContext, tc); gtc->set_active(true); return true; } /** * Uploads the texture image to the graphics state. * * The return value is true if successful, or false if the texture has no * image. */ bool TinyGraphicsStateGuardian:: upload_texture(TinyTextureContext *gtc, bool force, bool uses_mipmaps) { Texture *tex = gtc->get_texture(); if (_effective_incomplete_render && !force) { if (!tex->has_ram_image() && tex->might_have_ram_image() && tex->has_simple_ram_image() && !_loader.is_null()) { // If we don't have the texture data right now, go get it, but in the // meantime load a temporary simple image in its place. async_reload_texture(gtc); if (!tex->has_ram_image()) { if (gtc->was_simple_image_modified()) { return upload_simple_texture(gtc); } return true; } } } PStatTimer timer(_load_texture_pcollector); CPTA_uchar src_image = tex->get_uncompressed_ram_image(); if (src_image.is_null()) { return false; } #ifdef DO_PSTATS _data_transferred_pcollector.add_level(tex->get_ram_image_size()); #endif GLTexture *gltex = >c->_gltex; int num_levels = 1; if (uses_mipmaps) { num_levels = tex->get_expected_num_mipmap_levels(); if (tex->get_num_ram_mipmap_images() < num_levels) { tex->generate_ram_mipmap_images(); } } if (tinydisplay_cat.is_debug()) { tinydisplay_cat.debug() << "loading texture " << tex->get_name() << ", " << tex->get_x_size() << " x " << tex->get_y_size() << ", mipmaps = " << num_levels << ", uses_mipmaps = " << uses_mipmaps << "\n"; } if (!setup_gltex(gltex, tex->get_x_size(), tex->get_y_size(), num_levels)) { return false; } LColor border_color = tex->get_border_color(); border_color = border_color.fmin(LColor(1, 1, 1, 1)).fmax(LColor::zero()); gltex->border_color.v[0] = border_color[0]; gltex->border_color.v[1] = border_color[1]; gltex->border_color.v[2] = border_color[2]; gltex->border_color.v[3] = border_color[3]; int bytecount = 0; int xsize = gltex->xsize; int ysize = gltex->ysize; for (int level = 0; level < gltex->num_levels; ++level) { ZTextureLevel *dest = &gltex->levels[level]; switch (tex->get_format()) { case Texture::F_rgb: case Texture::F_rgb5: case Texture::F_rgb8: case Texture::F_rgb12: case Texture::F_rgb332: copy_rgb_image(dest, xsize, ysize, gtc, level); break; case Texture::F_rgba: case Texture::F_rgbm: case Texture::F_rgba4: case Texture::F_rgba5: case Texture::F_rgba8: case Texture::F_rgba12: case Texture::F_rgba16: case Texture::F_rgba32: copy_rgba_image(dest, xsize, ysize, gtc, level); break; case Texture::F_luminance: copy_lum_image(dest, xsize, ysize, gtc, level); break; case Texture::F_red: copy_one_channel_image(dest, xsize, ysize, gtc, level, 0); break; case Texture::F_green: copy_one_channel_image(dest, xsize, ysize, gtc, level, 1); break; case Texture::F_blue: copy_one_channel_image(dest, xsize, ysize, gtc, level, 2); break; case Texture::F_alpha: copy_alpha_image(dest, xsize, ysize, gtc, level); break; case Texture::F_luminance_alphamask: case Texture::F_luminance_alpha: copy_la_image(dest, xsize, ysize, gtc, level); break; default: tinydisplay_cat.error() << "Unsupported texture format " << tex->get_format() << "!\n"; return false; } bytecount += xsize * ysize * 4; xsize = max(xsize >> 1, 1); ysize = max(ysize >> 1, 1); } gtc->update_data_size_bytes(bytecount); get_engine()->texture_uploaded(tex); gtc->mark_loaded(); return true; } /** * This is used as a standin for upload_texture when the texture in question * is unavailable (e.g. it hasn't yet been loaded from disk). Until the * texture image itself becomes available, we will render the texture's * "simple" image--a sharply reduced version of the same texture. */ bool TinyGraphicsStateGuardian:: upload_simple_texture(TinyTextureContext *gtc) { PStatTimer timer(_load_texture_pcollector); Texture *tex = gtc->get_texture(); nassertr(tex != (Texture *)NULL, false); const unsigned char *image_ptr = tex->get_simple_ram_image(); if (image_ptr == (const unsigned char *)NULL) { return false; } size_t image_size = tex->get_simple_ram_image_size(); int width = tex->get_simple_x_size(); int height = tex->get_simple_y_size(); #ifdef DO_PSTATS _data_transferred_pcollector.add_level(image_size); #endif GLTexture *gltex = >c->_gltex; if (tinydisplay_cat.is_debug()) { tinydisplay_cat.debug() << "loading simple image for " << tex->get_name() << "\n"; } if (!setup_gltex(gltex, width, height, 1)) { return false; } LColor border_color = tex->get_border_color(); border_color = border_color.fmin(LColor(1, 1, 1, 1)).fmax(LColor::zero()); gltex->border_color.v[0] = border_color[0]; gltex->border_color.v[1] = border_color[1]; gltex->border_color.v[2] = border_color[2]; gltex->border_color.v[3] = border_color[3]; ZTextureLevel *dest = &gltex->levels[0]; memcpy(dest->pixmap, image_ptr, image_size); gtc->mark_simple_loaded(); return true; } /** * Sets the GLTexture size, bits, and masks appropriately, and allocates space * for a pixmap. Does not fill the pixmap contents. Returns true if the * texture is a valid size, false otherwise. */ bool TinyGraphicsStateGuardian:: setup_gltex(GLTexture *gltex, int x_size, int y_size, int num_levels) { int s_bits = get_tex_shift(x_size); int t_bits = get_tex_shift(y_size); if (s_bits < 0 || t_bits < 0) { tinydisplay_cat.error() << "Texture size " << x_size << 'x' << y_size << " unsupported: dimensions must be power of two" << " and smaller than " << _max_texture_dimension << '\n'; return false; } num_levels = min(num_levels, MAX_MIPMAP_LEVELS); gltex->xsize = x_size; gltex->ysize = y_size; gltex->s_max = 1 << (s_bits + ZB_POINT_ST_FRAC_BITS); gltex->t_max = 1 << (t_bits + ZB_POINT_ST_FRAC_BITS); gltex->num_levels = num_levels; // We allocate one big buffer, large enough to include all the mipmap // levels, and index into that buffer for each level. This cuts down on the // number of individual alloc calls we have to make for each texture. int total_bytecount = 0; // Count up the total bytes required for all mipmap levels. { int x = x_size; int y = y_size; for (int level = 0; level < num_levels; ++level) { int bytecount = x * y * 4; total_bytecount += bytecount; x = max((x >> 1), 1); y = max((y >> 1), 1); } } if (gltex->total_bytecount != total_bytecount) { if (gltex->allocated_buffer != NULL) { PANDA_FREE_ARRAY(gltex->allocated_buffer); TinyTextureContext::get_class_type().dec_memory_usage(TypeHandle::MC_array, gltex->total_bytecount); } gltex->allocated_buffer = PANDA_MALLOC_ARRAY(total_bytecount); gltex->total_bytecount = total_bytecount; TinyTextureContext::get_class_type().inc_memory_usage(TypeHandle::MC_array, total_bytecount); } char *next_buffer = (char *)gltex->allocated_buffer; char *end_of_buffer = next_buffer + total_bytecount; int level = 0; ZTextureLevel *dest = NULL; while (level < num_levels) { dest = &gltex->levels[level]; int bytecount = x_size * y_size * 4; dest->pixmap = (PIXEL *)next_buffer; next_buffer += bytecount; nassertr(next_buffer <= end_of_buffer, false); dest->s_mask = ((1 << (s_bits + ZB_POINT_ST_FRAC_BITS)) - (1 << ZB_POINT_ST_FRAC_BITS)) << level; dest->t_mask = ((1 << (t_bits + ZB_POINT_ST_FRAC_BITS)) - (1 << ZB_POINT_ST_FRAC_BITS)) << level; dest->s_shift = (ZB_POINT_ST_FRAC_BITS + level); dest->t_shift = (ZB_POINT_ST_FRAC_BITS - s_bits + level); x_size = max((x_size >> 1), 1); y_size = max((y_size >> 1), 1); s_bits = max(s_bits - 1, 0); t_bits = max(t_bits - 1, 0); ++level; } // Fill out the remaining mipmap arrays with copies of the last level, so we // don't have to be concerned with running off the end of this array while // scanning out triangles. while (level < MAX_MIPMAP_LEVELS) { gltex->levels[level] = *dest; ++level; } return true; } /** * Calculates the bit shift count, such that (1 << shift) == size. Returns -1 * if the size is not a power of 2 or is larger than our largest allowable * size. */ int TinyGraphicsStateGuardian:: get_tex_shift(int orig_size) { if ((orig_size & (orig_size - 1)) != 0) { // Not a power of 2. return -1; } if (orig_size > _max_texture_dimension) { return -1; } return count_bits_in_word((unsigned int)orig_size - 1); } /** * Copies and scales the one-channel luminance image from the texture into the * indicated ZTexture pixmap. */ void TinyGraphicsStateGuardian:: copy_lum_image(ZTextureLevel *dest, int xsize, int ysize, TinyTextureContext *gtc, int level) { Texture *tex = gtc->get_texture(); nassertv(tex->get_num_components() == 1); nassertv(tex->get_expected_mipmap_x_size(level) == xsize && tex->get_expected_mipmap_y_size(level) == ysize); CPTA_uchar src_image = tex->get_ram_mipmap_image(level); nassertv(!src_image.is_null()); const unsigned char *src = src_image.p(); size_t view_size = tex->get_ram_mipmap_view_size(level); src += view_size * gtc->get_view(); // Component width, and offset to the high-order byte. int cw = tex->get_component_width(); #ifdef WORDS_BIGENDIAN // Big-endian: the high-order byte is always first. static const int co = 0; #else // Little-endian: the high-order byte is last. int co = cw - 1; #endif unsigned int *dpix = (unsigned int *)dest->pixmap; nassertv(dpix != NULL); const unsigned char *spix = src; int pixel_count = xsize * ysize; while (pixel_count-- > 0) { *dpix = RGBA8_TO_PIXEL(spix[co], spix[co], spix[co], 0xff); ++dpix; spix += cw; } } /** * Copies and scales the one-channel alpha image from the texture into the * indicated ZTexture pixmap. */ void TinyGraphicsStateGuardian:: copy_alpha_image(ZTextureLevel *dest, int xsize, int ysize, TinyTextureContext *gtc, int level) { Texture *tex = gtc->get_texture(); nassertv(tex->get_num_components() == 1); CPTA_uchar src_image = tex->get_ram_mipmap_image(level); nassertv(!src_image.is_null()); const unsigned char *src = src_image.p(); size_t view_size = tex->get_ram_mipmap_view_size(level); src += view_size * gtc->get_view(); // Component width, and offset to the high-order byte. int cw = tex->get_component_width(); #ifdef WORDS_BIGENDIAN // Big-endian: the high-order byte is always first. static const int co = 0; #else // Little-endian: the high-order byte is last. int co = cw - 1; #endif unsigned int *dpix = (unsigned int *)dest->pixmap; nassertv(dpix != NULL); const unsigned char *spix = src; int pixel_count = xsize * ysize; while (pixel_count-- > 0) { *dpix = RGBA8_TO_PIXEL(0xff, 0xff, 0xff, spix[co]); ++dpix; spix += cw; } } /** * Copies and scales the one-channel image (with a single channel, e.g. red, * green, or blue) from the texture into the indicated ZTexture pixmap. */ void TinyGraphicsStateGuardian:: copy_one_channel_image(ZTextureLevel *dest, int xsize, int ysize, TinyTextureContext *gtc, int level, int channel) { Texture *tex = gtc->get_texture(); nassertv(tex->get_num_components() == 1); CPTA_uchar src_image = tex->get_ram_mipmap_image(level); nassertv(!src_image.is_null()); const unsigned char *src = src_image.p(); size_t view_size = tex->get_ram_mipmap_view_size(level); src += view_size * gtc->get_view(); // Component width, and offset to the high-order byte. int cw = tex->get_component_width(); #ifdef WORDS_BIGENDIAN // Big-endian: the high-order byte is always first. static const int co = 0; #else // Little-endian: the high-order byte is last. int co = cw - 1; #endif unsigned int *dpix = (unsigned int *)dest->pixmap; nassertv(dpix != NULL); const unsigned char *spix = src; int pixel_count = xsize * ysize; switch (channel) { case 0: while (pixel_count-- > 0) { *dpix = RGBA8_TO_PIXEL(spix[co], 0, 0, 0xff); ++dpix; spix += cw; } break; case 1: while (pixel_count-- > 0) { *dpix = RGBA8_TO_PIXEL(0, spix[co], 0, 0xff); ++dpix; spix += cw; } break; case 2: while (pixel_count-- > 0) { *dpix = RGBA8_TO_PIXEL(0, 0, spix[co], 0xff); ++dpix; spix += cw; } break; case 3: while (pixel_count-- > 0) { *dpix = RGBA8_TO_PIXEL(0, 0, 0, spix[co]); ++dpix; spix += cw; } break; } } /** * Copies and scales the two-channel luminance-alpha image from the texture * into the indicated ZTexture pixmap. */ void TinyGraphicsStateGuardian:: copy_la_image(ZTextureLevel *dest, int xsize, int ysize, TinyTextureContext *gtc, int level) { Texture *tex = gtc->get_texture(); nassertv(tex->get_num_components() == 2); CPTA_uchar src_image = tex->get_ram_mipmap_image(level); nassertv(!src_image.is_null()); const unsigned char *src = src_image.p(); size_t view_size = tex->get_ram_mipmap_view_size(level); src += view_size * gtc->get_view(); // Component width, and offset to the high-order byte. int cw = tex->get_component_width(); #ifdef WORDS_BIGENDIAN // Big-endian: the high-order byte is always first. static const int co = 0; #else // Little-endian: the high-order byte is last. int co = cw - 1; #endif unsigned int *dpix = (unsigned int *)dest->pixmap; nassertv(dpix != NULL); const unsigned char *spix = src; int pixel_count = xsize * ysize; int inc = 2 * cw; while (pixel_count-- > 0) { *dpix = RGBA8_TO_PIXEL(spix[co], spix[co], spix[co], spix[cw + co]); ++dpix; spix += inc; } } /** * Copies and scales the three-channel RGB image from the texture into the * indicated ZTexture pixmap. */ void TinyGraphicsStateGuardian:: copy_rgb_image(ZTextureLevel *dest, int xsize, int ysize, TinyTextureContext *gtc, int level) { Texture *tex = gtc->get_texture(); nassertv(tex->get_num_components() == 3); CPTA_uchar src_image = tex->get_ram_mipmap_image(level); nassertv(!src_image.is_null()); const unsigned char *src = src_image.p(); size_t view_size = tex->get_ram_mipmap_view_size(level); src += view_size * gtc->get_view(); // Component width, and offset to the high-order byte. int cw = tex->get_component_width(); #ifdef WORDS_BIGENDIAN // Big-endian: the high-order byte is always first. static const int co = 0; #else // Little-endian: the high-order byte is last. int co = cw - 1; #endif unsigned int *dpix = (unsigned int *)dest->pixmap; nassertv(dpix != NULL); const unsigned char *spix = src; int pixel_count = xsize * ysize; int inc = 3 * cw; while (pixel_count-- > 0) { *dpix = RGBA8_TO_PIXEL(spix[cw + cw + co], spix[cw + co], spix[co], 0xff); ++dpix; spix += inc; } } /** * Copies and scales the four-channel RGBA image from the texture into the * indicated ZTexture pixmap. */ void TinyGraphicsStateGuardian:: copy_rgba_image(ZTextureLevel *dest, int xsize, int ysize, TinyTextureContext *gtc, int level) { Texture *tex = gtc->get_texture(); nassertv(tex->get_num_components() == 4); CPTA_uchar src_image = tex->get_ram_mipmap_image(level); nassertv(!src_image.is_null()); const unsigned char *src = src_image.p(); size_t view_size = tex->get_ram_mipmap_view_size(level); src += view_size * gtc->get_view(); // Component width, and offset to the high-order byte. int cw = tex->get_component_width(); #ifdef WORDS_BIGENDIAN // Big-endian: the high-order byte is always first. static const int co = 0; #else // Little-endian: the high-order byte is last. int co = cw - 1; #endif unsigned int *dpix = (unsigned int *)dest->pixmap; nassertv(dpix != NULL); const unsigned char *spix = src; int pixel_count = xsize * ysize; int inc = 4 * cw; while (pixel_count-- > 0) { *dpix = RGBA8_TO_PIXEL(spix[cw + cw + co], spix[cw + co], spix[co], spix[cw + cw + cw + co]); ++dpix; spix += inc; } } /** * Applies the desired parametesr to the indicated GLMaterial object. */ void TinyGraphicsStateGuardian:: setup_material(GLMaterial *gl_material, const Material *material) { const LColor &specular = material->get_specular(); gl_material->specular.v[0] = specular[0]; gl_material->specular.v[1] = specular[1]; gl_material->specular.v[2] = specular[2]; gl_material->specular.v[3] = specular[3]; const LColor &emission = material->get_emission(); gl_material->emission.v[0] = emission[0]; gl_material->emission.v[1] = emission[1]; gl_material->emission.v[2] = emission[2]; gl_material->emission.v[3] = emission[3]; gl_material->shininess = material->get_shininess(); gl_material->shininess_i = (int)((material->get_shininess() / 128.0f) * SPECULAR_BUFFER_RESOLUTION); _color_material_flags = CMF_ambient | CMF_diffuse; if (material->has_ambient()) { const LColor &ambient = material->get_ambient(); gl_material->ambient.v[0] = ambient[0]; gl_material->ambient.v[1] = ambient[1]; gl_material->ambient.v[2] = ambient[2]; gl_material->ambient.v[3] = ambient[3]; _color_material_flags &= ~CMF_ambient; } if (material->has_diffuse()) { const LColor &diffuse = material->get_diffuse(); gl_material->diffuse.v[0] = diffuse[0]; gl_material->diffuse.v[1] = diffuse[1]; gl_material->diffuse.v[2] = diffuse[2]; gl_material->diffuse.v[3] = diffuse[3]; _color_material_flags &= ~CMF_diffuse; } } /** * Sets the state to either rescale or normalize the normals according to the * current transform. */ void TinyGraphicsStateGuardian:: do_auto_rescale_normal() { if (_internal_transform->has_uniform_scale()) { // There's a uniform scale; rescale the normals uniformly. _c->normalize_enabled = false; _c->normal_scale = _internal_transform->get_uniform_scale(); } else { // If there's a non-uniform scale, normalize everything. _c->normalize_enabled = true; _c->normal_scale = 1.0f; } } /** * Copies the Panda matrix stored in the indicated TransformState object into * the indicated TinyGL matrix. */ void TinyGraphicsStateGuardian:: load_matrix(M4 *matrix, const TransformState *transform) { const LMatrix4 &pm = transform->get_mat(); for (int i = 0; i < 4; ++i) { matrix->m[0][i] = pm.get_cell(i, 0); matrix->m[1][i] = pm.get_cell(i, 1); matrix->m[2][i] = pm.get_cell(i, 2); matrix->m[3][i] = pm.get_cell(i, 3); } } /** * Returns the integer element of store_pixel_funcs (as defined by * store_pixel.py) that corresponds to the indicated ColorBlendAttrib operand * code. */ int TinyGraphicsStateGuardian:: get_color_blend_op(ColorBlendAttrib::Operand operand) { switch (operand) { case ColorBlendAttrib::O_zero: return 0; case ColorBlendAttrib::O_one: return 1; case ColorBlendAttrib::O_incoming_color: return 2; case ColorBlendAttrib::O_one_minus_incoming_color: return 3; case ColorBlendAttrib::O_fbuffer_color: return 4; case ColorBlendAttrib::O_one_minus_fbuffer_color: return 5; case ColorBlendAttrib::O_incoming_alpha: return 6; case ColorBlendAttrib::O_one_minus_incoming_alpha: return 7; case ColorBlendAttrib::O_fbuffer_alpha: return 8; case ColorBlendAttrib::O_one_minus_fbuffer_alpha: return 9; case ColorBlendAttrib::O_constant_color: return 10; case ColorBlendAttrib::O_one_minus_constant_color: return 11; case ColorBlendAttrib::O_constant_alpha: return 12; case ColorBlendAttrib::O_one_minus_constant_alpha: return 13; case ColorBlendAttrib::O_incoming_color_saturate: return 1; case ColorBlendAttrib::O_color_scale: return 10; case ColorBlendAttrib::O_one_minus_color_scale: return 11; case ColorBlendAttrib::O_alpha_scale: return 12; case ColorBlendAttrib::O_one_minus_alpha_scale: return 13; } return 0; } /** * Returns the pointer to the appropriate filter function according to the * texture's filter type. */ ZB_lookupTextureFunc TinyGraphicsStateGuardian:: get_tex_filter_func(SamplerState::FilterType filter) { switch (filter) { case SamplerState::FT_nearest: return &lookup_texture_nearest; case SamplerState::FT_linear: return &lookup_texture_bilinear; case SamplerState::FT_nearest_mipmap_nearest: return &lookup_texture_mipmap_nearest; case SamplerState::FT_nearest_mipmap_linear: return &lookup_texture_mipmap_linear; case SamplerState::FT_linear_mipmap_nearest: return &lookup_texture_mipmap_bilinear; case SamplerState::FT_linear_mipmap_linear: return &lookup_texture_mipmap_trilinear; default: return &lookup_texture_nearest; } } /** * Returns the pointer to the appropriate wrap function according to the * texture's wrap mode. */ ZB_texWrapFunc TinyGraphicsStateGuardian:: get_tex_wrap_func(SamplerState::WrapMode wrap_mode) { switch (wrap_mode) { case SamplerState::WM_clamp: case SamplerState::WM_border_color: // border_color is handled later. return &texcoord_clamp; case SamplerState::WM_repeat: case SamplerState::WM_invalid: return &texcoord_repeat; case SamplerState::WM_mirror: return &texcoord_mirror; case SamplerState::WM_mirror_once: return &texcoord_mirror_once; } return &texcoord_repeat; } /** * Generates invalid texture coordinates. Used when texture coordinate params * are invalid or unsupported. */ void TinyGraphicsStateGuardian:: texgen_null(V2 &result, TinyGraphicsStateGuardian::TexCoordData &) { result.v[0] = 0.0; result.v[1] = 0.0; } /** * Extracts a simple 2-d texture coordinate pair from the vertex data, without * applying any texture matrix. */ void TinyGraphicsStateGuardian:: texgen_simple(V2 &result, TinyGraphicsStateGuardian::TexCoordData &tcdata) { // No need to transform, so just extract as two-component. const LVecBase2 &d = tcdata._r1.get_data2(); result.v[0] = d[0]; result.v[1] = d[1]; } /** * Extracts a simple 2-d texture coordinate pair from the vertex data, and * then applies a texture matrix. */ void TinyGraphicsStateGuardian:: texgen_texmat(V2 &result, TinyGraphicsStateGuardian::TexCoordData &tcdata) { // Transform texcoords as a four-component vector for most generality. LVecBase4 d = tcdata._r1.get_data4() * tcdata._mat; result.v[0] = d[0] / d[3]; result.v[1] = d[1] / d[3]; } /** * Computes appropriate sphere map texture coordinates based on the eye normal * coordinates. */ void TinyGraphicsStateGuardian:: texgen_sphere_map(V2 &result, TinyGraphicsStateGuardian::TexCoordData &tcdata) { // Get the normal and point in eye coordinates. LVector3 n = tcdata._mat.xform_vec(tcdata._r1.get_data3()); LVector3 u = tcdata._mat.xform_point(tcdata._r2.get_data3()); // Normalize the vectors. n.normalize(); u.normalize(); // Compute the reflection vector. LVector3 r = u - n * dot(n, u) * 2.0f; // compute the denominator, m. PN_stdfloat m = 2.0f * csqrt(r[0] * r[0] + r[1] * r[1] + (r[2] + 1.0f) * (r[2] + 1.0f)); // Now we can compute the s and t coordinates. result.v[0] = r[0] / m + 0.5f; result.v[1] = r[1] / m + 0.5f; /* cerr << "n = " << n << " u = " << u << "\n" << " r = " << r << "\n" << " m = " << m << ", result = " << result.v[0] << " " << result.v[1] << "\n"; */ }