mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-04 19:08:55 -04:00
first pass at MultitexReducer
This commit is contained in:
parent
8ea3435458
commit
5761ae9d2c
@ -38,7 +38,6 @@ PUBLISHED:
|
|||||||
INLINE bool operator != (const FrameBufferProperties &other) const;
|
INLINE bool operator != (const FrameBufferProperties &other) const;
|
||||||
|
|
||||||
enum FrameBufferMode {
|
enum FrameBufferMode {
|
||||||
FM_rgba = 0x0000,
|
|
||||||
FM_rgb = 0x0000,
|
FM_rgb = 0x0000,
|
||||||
FM_index = 0x0001,
|
FM_index = 0x0001,
|
||||||
FM_single_buffer = 0x0000,
|
FM_single_buffer = 0x0000,
|
||||||
@ -47,6 +46,7 @@ PUBLISHED:
|
|||||||
FM_buffer = 0x0006, // == (FM_single_buffer | FM_double_buffer | FM_triple_buffer)
|
FM_buffer = 0x0006, // == (FM_single_buffer | FM_double_buffer | FM_triple_buffer)
|
||||||
FM_accum = 0x0008,
|
FM_accum = 0x0008,
|
||||||
FM_alpha = 0x0010,
|
FM_alpha = 0x0010,
|
||||||
|
FM_rgba = 0x0010, // == (FM_rgb | FM_alpha)
|
||||||
FM_depth = 0x0020,
|
FM_depth = 0x0020,
|
||||||
FM_stencil = 0x0040,
|
FM_stencil = 0x0040,
|
||||||
FM_multisample = 0x0080,
|
FM_multisample = 0x0080,
|
||||||
|
@ -383,7 +383,7 @@ remove_all_windows() {
|
|||||||
GraphicsOutput *win = (*wi);
|
GraphicsOutput *win = (*wi);
|
||||||
do_remove_window(win);
|
do_remove_window(win);
|
||||||
}
|
}
|
||||||
|
|
||||||
_windows.clear();
|
_windows.clear();
|
||||||
|
|
||||||
_app.do_release(this);
|
_app.do_release(this);
|
||||||
@ -449,6 +449,21 @@ render_frame() {
|
|||||||
if (_flip_state != FS_flip) {
|
if (_flip_state != FS_flip) {
|
||||||
do_flip_frame();
|
do_flip_frame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Are any of the windows ready to be deleted?
|
||||||
|
Windows new_windows;
|
||||||
|
new_windows.reserve(_windows.size());
|
||||||
|
Windows::iterator wi;
|
||||||
|
for (wi = _windows.begin(); wi != _windows.end(); ++wi) {
|
||||||
|
GraphicsOutput *win = (*wi);
|
||||||
|
if (win->get_delete_flag()) {
|
||||||
|
do_remove_window(win);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
new_windows.push_back(win);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
new_windows.swap(_windows);
|
||||||
|
|
||||||
// Grab each thread's mutex again after all windows have flipped.
|
// Grab each thread's mutex again after all windows have flipped.
|
||||||
Threads::const_iterator ti;
|
Threads::const_iterator ti;
|
||||||
@ -1303,7 +1318,7 @@ add_window(Windows &wlist, GraphicsOutput *window) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
// Function: GraphicsEngine::WindowRenderer::remove_window_now
|
// Function: GraphicsEngine::WindowRenderer::remove_window
|
||||||
// Access: Public
|
// Access: Public
|
||||||
// Description: Immediately removes the indicated window from all
|
// Description: Immediately removes the indicated window from all
|
||||||
// lists. If the window is currently open and is
|
// lists. If the window is currently open and is
|
||||||
|
@ -160,6 +160,59 @@ is_valid() const {
|
|||||||
return _is_valid;
|
return _is_valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// Function: GraphicsOutput::set_one_shot
|
||||||
|
// Access: Published
|
||||||
|
// Description: Changes the current setting of the one-shot flag.
|
||||||
|
// When this is true, the GraphicsOutput will
|
||||||
|
// automatically detach its texture (if it has one) and
|
||||||
|
// remove itself from the GraphicsEngine after it
|
||||||
|
// renders the next frame.
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
INLINE void GraphicsOutput::
|
||||||
|
set_one_shot(bool one_shot) {
|
||||||
|
_one_shot = one_shot;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// Function: GraphicsOutput::get_one_shot
|
||||||
|
// Access: Published
|
||||||
|
// Description: Returns the current setting of the one-shot flag.
|
||||||
|
// When this is true, the GraphicsOutput will
|
||||||
|
// automatically detach its texture (if it has one) and
|
||||||
|
// remove itself from the GraphicsEngine after it
|
||||||
|
// renders the next frame.
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
INLINE bool GraphicsOutput::
|
||||||
|
get_one_shot() const {
|
||||||
|
return _one_shot;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// Function: GraphicsOutput::clear_delete_flag
|
||||||
|
// Access: Published
|
||||||
|
// Description: Resets the delete flag, so the GraphicsOutput will
|
||||||
|
// not be automatically deleted before the beginning of
|
||||||
|
// the next frame.
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
INLINE void GraphicsOutput::
|
||||||
|
clear_delete_flag() {
|
||||||
|
_delete_flag = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// Function: GraphicsOutput::get_delete_flag
|
||||||
|
// Access: Published
|
||||||
|
// Description: Returns the current setting of the delete flag. When
|
||||||
|
// this is true, the GraphicsOutput will automatically
|
||||||
|
// be removed before the beginning of the next frame by
|
||||||
|
// the GraphicsEngine.
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
INLINE bool GraphicsOutput::
|
||||||
|
get_delete_flag() const {
|
||||||
|
return _delete_flag;
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
// Function: GraphicsOutput::get_sort
|
// Function: GraphicsOutput::get_sort
|
||||||
// Access: Published
|
// Access: Published
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include "graphicsOutput.h"
|
#include "graphicsOutput.h"
|
||||||
#include "graphicsPipe.h"
|
#include "graphicsPipe.h"
|
||||||
#include "graphicsEngine.h"
|
#include "graphicsEngine.h"
|
||||||
|
#include "graphicsWindow.h"
|
||||||
#include "config_display.h"
|
#include "config_display.h"
|
||||||
#include "mutexHolder.h"
|
#include "mutexHolder.h"
|
||||||
#include "hardwareChannel.h"
|
#include "hardwareChannel.h"
|
||||||
@ -54,6 +55,9 @@ GraphicsOutput(GraphicsPipe *pipe, GraphicsStateGuardian *gsg,
|
|||||||
_flip_ready = false;
|
_flip_ready = false;
|
||||||
_needs_context = true;
|
_needs_context = true;
|
||||||
_sort = 0;
|
_sort = 0;
|
||||||
|
_active = true;
|
||||||
|
_one_shot = false;
|
||||||
|
_delete_flag = false;
|
||||||
|
|
||||||
int mode = gsg->get_properties().get_frame_buffer_mode();
|
int mode = gsg->get_properties().get_frame_buffer_mode();
|
||||||
if ((mode & FrameBufferProperties::FM_buffer) == FrameBufferProperties::FM_single_buffer) {
|
if ((mode & FrameBufferProperties::FM_buffer) == FrameBufferProperties::FM_single_buffer) {
|
||||||
@ -112,6 +116,73 @@ GraphicsOutput::
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// Function: GraphicsOutput::detach_texture
|
||||||
|
// Access: Published
|
||||||
|
// Description: Disassociates the texture from the GraphicsOutput.
|
||||||
|
// It will no longer be filled as the frame renders, and
|
||||||
|
// it may be used (with its current contents) as a
|
||||||
|
// texture in its own right.
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
void GraphicsOutput::
|
||||||
|
detach_texture() {
|
||||||
|
MutexHolder holder(_lock);
|
||||||
|
_texture = NULL;
|
||||||
|
_copy_texture = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// Function: GraphicsOutput::setup_copy_texture
|
||||||
|
// Access: Published
|
||||||
|
// Description: Creates a new Texture object, suitable for copying
|
||||||
|
// the contents of this buffer into, and stores it in
|
||||||
|
// _texture. This also disassociates the previous
|
||||||
|
// texture (if any).
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
void GraphicsOutput::
|
||||||
|
setup_copy_texture(const string &name) {
|
||||||
|
MutexHolder holder(_lock);
|
||||||
|
|
||||||
|
_texture = new Texture();
|
||||||
|
_texture->set_name(name);
|
||||||
|
_texture->set_wrapu(Texture::WM_clamp);
|
||||||
|
_texture->set_wrapv(Texture::WM_clamp);
|
||||||
|
|
||||||
|
// We should match the texture format up with the framebuffer
|
||||||
|
// format. Easier said than done!
|
||||||
|
if (_gsg != (GraphicsStateGuardian *)NULL) {
|
||||||
|
int mode = _gsg->get_properties().get_frame_buffer_mode();
|
||||||
|
PixelBuffer *pb = _texture->_pbuffer;
|
||||||
|
|
||||||
|
if (mode & FrameBufferProperties::FM_alpha) {
|
||||||
|
pb->set_format(PixelBuffer::F_rgba8);
|
||||||
|
pb->set_num_components(4);
|
||||||
|
pb->set_component_width(1);
|
||||||
|
pb->set_image_type(PixelBuffer::T_unsigned_byte);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
pb->set_format(PixelBuffer::F_rgb8);
|
||||||
|
pb->set_num_components(3);
|
||||||
|
pb->set_component_width(1);
|
||||||
|
pb->set_image_type(PixelBuffer::T_unsigned_byte);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_copy_texture = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// Function: GraphicsOutput::set_active
|
||||||
|
// Access: Published
|
||||||
|
// Description: Sets the active flag associated with the
|
||||||
|
// GraphicsOutput. If the GraphicsOutput is marked
|
||||||
|
// inactive, nothing is rendered.
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
void GraphicsOutput::
|
||||||
|
set_active(bool active) {
|
||||||
|
_active = active;
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
// Function: GraphicsOutput::is_active
|
// Function: GraphicsOutput::is_active
|
||||||
// Access: Published, Virtual
|
// Access: Published, Virtual
|
||||||
@ -120,7 +191,7 @@ GraphicsOutput::
|
|||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
bool GraphicsOutput::
|
bool GraphicsOutput::
|
||||||
is_active() const {
|
is_active() const {
|
||||||
return is_valid();
|
return _active && is_valid();
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
@ -571,6 +642,15 @@ end_frame() {
|
|||||||
if (!_gsg->get_properties().is_single_buffered()) {
|
if (!_gsg->get_properties().is_single_buffered()) {
|
||||||
_flip_ready = true;
|
_flip_ready = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_one_shot && !show_buffers) {
|
||||||
|
// In one-shot mode, we request the GraphicsEngine to delete the
|
||||||
|
// window after we have rendered a frame. But when show-buffers
|
||||||
|
// mode is enabled, we don't do this, to give the user a chance to
|
||||||
|
// see the output.
|
||||||
|
_active = false;
|
||||||
|
_delete_flag = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
@ -684,43 +764,6 @@ declare_channel(int index, GraphicsChannel *chan) {
|
|||||||
_channels[index] = chan;
|
_channels[index] = chan;
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////
|
|
||||||
// Function: GraphicsOutput::setup_copy_texture
|
|
||||||
// Access: Protected
|
|
||||||
// Description: Creates a new Texture object, suitable for copying
|
|
||||||
// the contents of this buffer into, and stores it in
|
|
||||||
// _texture.
|
|
||||||
////////////////////////////////////////////////////////////////////
|
|
||||||
void GraphicsOutput::
|
|
||||||
setup_copy_texture(const string &name) {
|
|
||||||
_texture = new Texture();
|
|
||||||
_texture->set_name(name);
|
|
||||||
_texture->set_wrapu(Texture::WM_clamp);
|
|
||||||
_texture->set_wrapv(Texture::WM_clamp);
|
|
||||||
|
|
||||||
// We should match the texture format up with the framebuffer
|
|
||||||
// format. Easier said than done!
|
|
||||||
if (_gsg != (GraphicsStateGuardian *)NULL) {
|
|
||||||
int mode = _gsg->get_properties().get_frame_buffer_mode();
|
|
||||||
PixelBuffer *pb = _texture->_pbuffer;
|
|
||||||
|
|
||||||
if (mode & FrameBufferProperties::FM_alpha) {
|
|
||||||
pb->set_format(PixelBuffer::F_rgba8);
|
|
||||||
pb->set_num_components(4);
|
|
||||||
pb->set_component_width(1);
|
|
||||||
pb->set_image_type(PixelBuffer::T_unsigned_byte);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
pb->set_format(PixelBuffer::F_rgb8);
|
|
||||||
pb->set_num_components(3);
|
|
||||||
pb->set_component_width(1);
|
|
||||||
pb->set_image_type(PixelBuffer::T_unsigned_byte);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_copy_texture = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
// Function: GraphicsOutput::do_determine_display_regions
|
// Function: GraphicsOutput::do_determine_display_regions
|
||||||
// Access: Private
|
// Access: Private
|
||||||
|
@ -75,16 +75,25 @@ PUBLISHED:
|
|||||||
|
|
||||||
INLINE bool has_texture() const;
|
INLINE bool has_texture() const;
|
||||||
INLINE Texture *get_texture() const;
|
INLINE Texture *get_texture() const;
|
||||||
|
void detach_texture();
|
||||||
|
void setup_copy_texture(const string &name);
|
||||||
|
|
||||||
INLINE int get_x_size() const;
|
INLINE int get_x_size() const;
|
||||||
INLINE int get_y_size() const;
|
INLINE int get_y_size() const;
|
||||||
INLINE bool has_size() const;
|
INLINE bool has_size() const;
|
||||||
INLINE bool is_valid() const;
|
INLINE bool is_valid() const;
|
||||||
|
|
||||||
|
void set_active(bool active);
|
||||||
virtual bool is_active() const;
|
virtual bool is_active() const;
|
||||||
|
|
||||||
INLINE int get_sort() const;
|
INLINE void set_one_shot(bool one_shot);
|
||||||
|
INLINE bool get_one_shot() const;
|
||||||
|
|
||||||
|
INLINE void clear_delete_flag();
|
||||||
|
INLINE bool get_delete_flag() const;
|
||||||
|
|
||||||
void set_sort(int sort);
|
void set_sort(int sort);
|
||||||
|
INLINE int get_sort() const;
|
||||||
|
|
||||||
GraphicsChannel *get_channel(int index);
|
GraphicsChannel *get_channel(int index);
|
||||||
void remove_channel(int index);
|
void remove_channel(int index);
|
||||||
@ -145,7 +154,6 @@ public:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
void declare_channel(int index, GraphicsChannel *chan);
|
void declare_channel(int index, GraphicsChannel *chan);
|
||||||
void setup_copy_texture(const string &name);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
PT(GraphicsStateGuardian) _gsg;
|
PT(GraphicsStateGuardian) _gsg;
|
||||||
@ -162,6 +170,11 @@ private:
|
|||||||
|
|
||||||
int _sort;
|
int _sort;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool _active;
|
||||||
|
bool _one_shot;
|
||||||
|
bool _delete_flag;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Mutex _lock;
|
Mutex _lock;
|
||||||
// protects _channels, _display_regions.
|
// protects _channels, _display_regions.
|
||||||
|
@ -160,7 +160,7 @@ request_properties(const WindowProperties &requested_properties) {
|
|||||||
bool GraphicsWindow::
|
bool GraphicsWindow::
|
||||||
is_active() const {
|
is_active() const {
|
||||||
// Make this smarter?
|
// Make this smarter?
|
||||||
return _properties.get_open() && !_properties.get_minimized();
|
return _active && _properties.get_open() && !_properties.get_minimized();
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
|
@ -73,7 +73,7 @@ ParasiteBuffer::
|
|||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
bool ParasiteBuffer::
|
bool ParasiteBuffer::
|
||||||
is_active() const {
|
is_active() const {
|
||||||
return _host->is_active();
|
return _active && _host->is_active();
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
|
@ -3109,7 +3109,19 @@ apply_tex_mat(CPT(RenderAttrib) tex_mat_attrib,
|
|||||||
tex_mat(1, 0), tex_mat(1, 1), 0.0f, tex_mat(1, 2),
|
tex_mat(1, 0), tex_mat(1, 1), 0.0f, tex_mat(1, 2),
|
||||||
0.0f, 0.0f, 1.0f, 0.0f,
|
0.0f, 0.0f, 1.0f, 0.0f,
|
||||||
tex_mat(2, 0), tex_mat(2, 1), 0.0f, tex_mat(2, 2));
|
tex_mat(2, 0), tex_mat(2, 1), 0.0f, tex_mat(2, 2));
|
||||||
CPT(TransformState) transform = TransformState::make_mat(mat4);
|
CPT(TransformState) transform;
|
||||||
|
|
||||||
|
LVecBase3f scale, shear, hpr, translate;
|
||||||
|
if (decompose_matrix(mat4, scale, shear, hpr, translate)) {
|
||||||
|
// If the texture matrix can be represented componentwise, do
|
||||||
|
// so.
|
||||||
|
transform = TransformState::make_pos_hpr_scale_shear
|
||||||
|
(translate, hpr, scale, shear);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Otherwise, make a matrix transform.
|
||||||
|
transform = TransformState::make_mat(mat4);
|
||||||
|
}
|
||||||
|
|
||||||
if (tex_mat_attrib == (const RenderAttrib *)NULL) {
|
if (tex_mat_attrib == (const RenderAttrib *)NULL) {
|
||||||
tex_mat_attrib = TexMatrixAttrib::make();
|
tex_mat_attrib = TexMatrixAttrib::make();
|
||||||
|
@ -2030,27 +2030,7 @@ copy_texture(Texture *tex, const DisplayRegion *dr) {
|
|||||||
nassertv(tex != NULL && dr != NULL);
|
nassertv(tex != NULL && dr != NULL);
|
||||||
|
|
||||||
int xo, yo, w, h;
|
int xo, yo, w, h;
|
||||||
|
|
||||||
#if 0
|
|
||||||
// Determine the size of the grab from the given display region
|
|
||||||
// If the requested region is not a power of two, grab a region that is
|
|
||||||
// a power of two that contains the requested region
|
|
||||||
int req_w, req_h;
|
|
||||||
dr->get_region_pixels(xo, yo, req_w, req_h);
|
|
||||||
w = binary_log_cap(req_w);
|
|
||||||
h = binary_log_cap(req_h);
|
|
||||||
if (w != req_w || h != req_h) {
|
|
||||||
tex->_requested_w = req_w;
|
|
||||||
tex->_requested_h = req_h;
|
|
||||||
tex->_has_requested_size = true;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
// doing the above is bad unless you also provide some way
|
|
||||||
// for the caller to adjust his texture coordinates accordingly
|
|
||||||
// this was done for 'draw_texture' but not for anything else
|
|
||||||
|
|
||||||
dr->get_region_pixels(xo, yo, w, h);
|
dr->get_region_pixels(xo, yo, w, h);
|
||||||
#endif
|
|
||||||
|
|
||||||
PixelBuffer *pb = tex->_pbuffer;
|
PixelBuffer *pb = tex->_pbuffer;
|
||||||
pb->set_xsize(w);
|
pb->set_xsize(w);
|
||||||
@ -3230,8 +3210,7 @@ specify_texture(Texture *tex) {
|
|||||||
|
|
||||||
Texture::FilterType minfilter = tex->get_minfilter();
|
Texture::FilterType minfilter = tex->get_minfilter();
|
||||||
Texture::FilterType magfilter = tex->get_magfilter();
|
Texture::FilterType magfilter = tex->get_magfilter();
|
||||||
|
bool uses_mipmaps = tex->uses_mipmaps() && !CLP(ignore_mipmaps);
|
||||||
bool uses_mipmaps = tex->uses_mipmaps();
|
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
if (CLP(force_mipmaps)) {
|
if (CLP(force_mipmaps)) {
|
||||||
@ -3240,11 +3219,25 @@ specify_texture(Texture *tex) {
|
|||||||
uses_mipmaps = true;
|
uses_mipmaps = true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (_supports_generate_mipmap) {
|
||||||
|
// If the hardware can automatically generate mipmaps, ask it to
|
||||||
|
// do so now, but only if the texture requires them.
|
||||||
|
GLP(TexParameteri)(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, uses_mipmaps);
|
||||||
|
|
||||||
|
} else if (!tex->might_have_ram_image()) {
|
||||||
|
// If the hardware can't automatically generate mipmaps, but it's
|
||||||
|
// a dynamically generated texture (that is, the RAM image isn't
|
||||||
|
// available so it didn't pass through the CPU), then we'd better
|
||||||
|
// not try to enable mipmap filtering, since we can't generate
|
||||||
|
// mipmaps.
|
||||||
|
uses_mipmaps = false;
|
||||||
|
}
|
||||||
|
|
||||||
GLP(TexParameteri)(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
|
GLP(TexParameteri)(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
|
||||||
get_texture_filter_type(tex->get_minfilter()));
|
get_texture_filter_type(minfilter, !uses_mipmaps));
|
||||||
GLP(TexParameteri)(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
|
GLP(TexParameteri)(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
|
||||||
get_texture_filter_type(tex->get_magfilter()));
|
get_texture_filter_type(magfilter, true));
|
||||||
|
|
||||||
report_my_gl_errors();
|
report_my_gl_errors();
|
||||||
}
|
}
|
||||||
@ -3378,10 +3371,7 @@ apply_texture_immediate(CLP(TextureContext) *gtc, Texture *tex) {
|
|||||||
|
|
||||||
} else
|
} else
|
||||||
#endif
|
#endif
|
||||||
if (_supports_generate_mipmap) {
|
if (!_supports_generate_mipmap) {
|
||||||
GLP(TexParameteri)(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// We only need to build the mipmaps by hand if the GL
|
// We only need to build the mipmaps by hand if the GL
|
||||||
// doesn't support generating them automatically.
|
// doesn't support generating them automatically.
|
||||||
GLUP(Build2DMipmaps)(GL_TEXTURE_2D, internal_format,
|
GLUP(Build2DMipmaps)(GL_TEXTURE_2D, internal_format,
|
||||||
@ -3402,10 +3392,6 @@ apply_texture_immediate(CLP(TextureContext) *gtc, Texture *tex) {
|
|||||||
report_my_gl_errors();
|
report_my_gl_errors();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (_supports_generate_mipmap) {
|
|
||||||
GLP(TexParameteri)(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_FALSE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GLint border_width = tex->get_border_width();
|
GLint border_width = tex->get_border_width();
|
||||||
@ -3711,11 +3697,11 @@ get_texture_wrap_mode(Texture::WrapMode wm) const {
|
|||||||
// to GL's.
|
// to GL's.
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
GLenum CLP(GraphicsStateGuardian)::
|
GLenum CLP(GraphicsStateGuardian)::
|
||||||
get_texture_filter_type(Texture::FilterType ft) {
|
get_texture_filter_type(Texture::FilterType ft, bool ignore_mipmaps) {
|
||||||
if (CLP(ignore_filters)) {
|
if (CLP(ignore_filters)) {
|
||||||
return GL_NEAREST;
|
return GL_NEAREST;
|
||||||
|
|
||||||
} else if (CLP(ignore_mipmaps)) {
|
} else if (ignore_mipmaps) {
|
||||||
switch (ft) {
|
switch (ft) {
|
||||||
case Texture::FT_nearest_mipmap_nearest:
|
case Texture::FT_nearest_mipmap_nearest:
|
||||||
case Texture::FT_nearest:
|
case Texture::FT_nearest:
|
||||||
|
@ -217,7 +217,7 @@ protected:
|
|||||||
const RenderBuffer &rb);
|
const RenderBuffer &rb);
|
||||||
|
|
||||||
GLenum get_texture_wrap_mode(Texture::WrapMode wm) const;
|
GLenum get_texture_wrap_mode(Texture::WrapMode wm) const;
|
||||||
static GLenum get_texture_filter_type(Texture::FilterType ft);
|
static GLenum get_texture_filter_type(Texture::FilterType ft, bool ignore_mipmaps);
|
||||||
static GLenum get_image_type(PixelBuffer::Type type);
|
static GLenum get_image_type(PixelBuffer::Type type);
|
||||||
GLint get_external_image_format(PixelBuffer::Format format) const;
|
GLint get_external_image_format(PixelBuffer::Format format) const;
|
||||||
static GLint get_internal_image_format(PixelBuffer::Format format);
|
static GLint get_internal_image_format(PixelBuffer::Format format);
|
||||||
|
@ -110,7 +110,7 @@ uses_mipmaps() const {
|
|||||||
// Description: Returns true if the Texture has its image contents
|
// Description: Returns true if the Texture has its image contents
|
||||||
// available in main RAM, false if it exists only in
|
// available in main RAM, false if it exists only in
|
||||||
// texture memory or in the prepared GSG context.
|
// texture memory or in the prepared GSG context.
|
||||||
|
//
|
||||||
// Note that this has nothing to do with whether
|
// Note that this has nothing to do with whether
|
||||||
// get_ram_image() will fail or not. Even if
|
// get_ram_image() will fail or not. Even if
|
||||||
// has_ram_image() returns false, get_ram_image() may
|
// has_ram_image() returns false, get_ram_image() may
|
||||||
@ -137,6 +137,21 @@ has_ram_image() const {
|
|||||||
return !_pbuffer->_image.empty();
|
return !_pbuffer->_image.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// Function: Texture::might_have_ram_image
|
||||||
|
// Access: Public
|
||||||
|
// Description: Returns true if the texture's image contents are
|
||||||
|
// currently available in main RAM, or there is reason
|
||||||
|
// to believe it can be loaded on demand. That is, this
|
||||||
|
// function returns a "best guess" as to whether
|
||||||
|
// get_ram_image() will succeed without actually calling
|
||||||
|
// it first.
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
INLINE bool Texture::
|
||||||
|
might_have_ram_image() const {
|
||||||
|
return (has_ram_image() || has_filename());
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
// Function: Texture::set_keep_ram_image
|
// Function: Texture::set_keep_ram_image
|
||||||
// Access: Public
|
// Access: Public
|
||||||
|
@ -619,12 +619,16 @@ release_all() {
|
|||||||
// false return value from has_ram_image() indicates
|
// false return value from has_ram_image() indicates
|
||||||
// only that get_ram_image() may need to reload the
|
// only that get_ram_image() may need to reload the
|
||||||
// texture from disk, which it will do automatically.
|
// texture from disk, which it will do automatically.
|
||||||
|
// However, you can call might_have_ram_image(), which
|
||||||
|
// will return true if the ram image exists, or there is
|
||||||
|
// a reasonable reason to believe it can be loaded.
|
||||||
//
|
//
|
||||||
// On the other hand, it is possible that the texture
|
// On the other hand, it is possible that the texture
|
||||||
// cannot be found on disk or is otherwise unavailable.
|
// cannot be found on disk or is otherwise unavailable.
|
||||||
// If that happens, this function returns NULL. There
|
// If that happens, this function will return NULL.
|
||||||
// is no way to predict whether get_ram_image() will
|
// There is no way to predict with 100% accuracy whether
|
||||||
// return NULL without calling it first.
|
// get_ram_image() will return NULL without calling it
|
||||||
|
// first; might_have_ram_image() is the closest.
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
PixelBuffer *Texture::
|
PixelBuffer *Texture::
|
||||||
get_ram_image() {
|
get_ram_image() {
|
||||||
|
@ -116,6 +116,7 @@ public:
|
|||||||
int release_all();
|
int release_all();
|
||||||
|
|
||||||
INLINE bool has_ram_image() const;
|
INLINE bool has_ram_image() const;
|
||||||
|
INLINE bool might_have_ram_image() const;
|
||||||
PixelBuffer *get_ram_image();
|
PixelBuffer *get_ram_image();
|
||||||
INLINE void set_keep_ram_image(bool keep_ram_image);
|
INLINE void set_keep_ram_image(bool keep_ram_image);
|
||||||
INLINE bool get_keep_ram_image() const;
|
INLINE bool get_keep_ram_image() const;
|
||||||
|
@ -12,7 +12,8 @@
|
|||||||
cardMaker.I cardMaker.h \
|
cardMaker.I cardMaker.h \
|
||||||
config_grutil.h \
|
config_grutil.h \
|
||||||
frameRateMeter.I frameRateMeter.h \
|
frameRateMeter.I frameRateMeter.h \
|
||||||
lineSegs.I lineSegs.h
|
lineSegs.I lineSegs.h \
|
||||||
|
multitexReducer.I multitexReducer.h multitexReducer.cxx
|
||||||
|
|
||||||
#define INCLUDED_SOURCES \
|
#define INCLUDED_SOURCES \
|
||||||
cardMaker.cxx \
|
cardMaker.cxx \
|
||||||
|
50
panda/src/grutil/multitexReducer.I
Normal file
50
panda/src/grutil/multitexReducer.I
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// Filename: multitexReducer.I
|
||||||
|
// Created by: drose (30Nov04)
|
||||||
|
//
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// PANDA 3D SOFTWARE
|
||||||
|
// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved
|
||||||
|
//
|
||||||
|
// All use of this software is subject to the terms of the Panda 3d
|
||||||
|
// Software license. You should have received a copy of this license
|
||||||
|
// along with this source code; you will also find a current copy of
|
||||||
|
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
|
||||||
|
//
|
||||||
|
// To contact the maintainers of this program write to
|
||||||
|
// panda3d-general@lists.sourceforge.net .
|
||||||
|
//
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// Function: MultitexReducer::StageInfo::operator <
|
||||||
|
// Access: Public
|
||||||
|
// Description:
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
INLINE bool MultitexReducer::StageInfo::
|
||||||
|
operator < (const MultitexReducer::StageInfo &other) const {
|
||||||
|
if (_stage != other._stage) {
|
||||||
|
return _stage < other._stage;
|
||||||
|
}
|
||||||
|
if (_tex != other._tex) {
|
||||||
|
return _tex < other._tex;
|
||||||
|
}
|
||||||
|
if (_tex_mat != other._tex_mat) {
|
||||||
|
return _tex_mat < other._tex_mat;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// Function: MultitexReducer::GeomInfo::Constructor
|
||||||
|
// Access: Public
|
||||||
|
// Description:
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
INLINE MultitexReducer::GeomInfo::
|
||||||
|
GeomInfo(GeomNode *geom_node, int index) :
|
||||||
|
_geom_node(geom_node),
|
||||||
|
_index(index)
|
||||||
|
{
|
||||||
|
}
|
486
panda/src/grutil/multitexReducer.cxx
Normal file
486
panda/src/grutil/multitexReducer.cxx
Normal file
@ -0,0 +1,486 @@
|
|||||||
|
// Filename: multitexReducer.cxx
|
||||||
|
// Created by: drose (30Nov04)
|
||||||
|
//
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// PANDA 3D SOFTWARE
|
||||||
|
// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved
|
||||||
|
//
|
||||||
|
// All use of this software is subject to the terms of the Panda 3d
|
||||||
|
// Software license. You should have received a copy of this license
|
||||||
|
// along with this source code; you will also find a current copy of
|
||||||
|
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
|
||||||
|
//
|
||||||
|
// To contact the maintainers of this program write to
|
||||||
|
// panda3d-general@lists.sourceforge.net .
|
||||||
|
//
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#include "multitexReducer.h"
|
||||||
|
#include "pandaNode.h"
|
||||||
|
#include "geomNode.h"
|
||||||
|
#include "geom.h"
|
||||||
|
#include "renderState.h"
|
||||||
|
#include "transformState.h"
|
||||||
|
#include "graphicsOutput.h"
|
||||||
|
#include "graphicsChannel.h"
|
||||||
|
#include "graphicsLayer.h"
|
||||||
|
#include "displayRegion.h"
|
||||||
|
#include "nodePath.h"
|
||||||
|
#include "camera.h"
|
||||||
|
#include "orthographicLens.h"
|
||||||
|
#include "cardMaker.h"
|
||||||
|
#include "colorBlendAttrib.h"
|
||||||
|
#include "config_grutil.h"
|
||||||
|
#include "dcast.h"
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// Function: MultitexReducer::Constructor
|
||||||
|
// Access: Published
|
||||||
|
// Description:
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
MultitexReducer::
|
||||||
|
MultitexReducer() {
|
||||||
|
_target_stage = TextureStage::get_default();
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// Function: MultitexReducer::Destructor
|
||||||
|
// Access: Published
|
||||||
|
// Description:
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
MultitexReducer::
|
||||||
|
~MultitexReducer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// Function: MultitexReducer::clear
|
||||||
|
// Access: Published
|
||||||
|
// Description: Removes the record of nodes that were previously
|
||||||
|
// discovered by scan().
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
void MultitexReducer::
|
||||||
|
clear() {
|
||||||
|
_stages.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// Function: MultitexReducer::scan
|
||||||
|
// Access: Published
|
||||||
|
// Description: Starts scanning the hierarchy beginning at the
|
||||||
|
// indicated node. Any GeomNodes discovered in the
|
||||||
|
// hierarchy with multitexture will be added to internal
|
||||||
|
// structures in the MultitexReducer so that a future
|
||||||
|
// call to flatten() will operate on all of these at
|
||||||
|
// once.
|
||||||
|
//
|
||||||
|
// The indicated transform and state are the state
|
||||||
|
// inherited from the node's ancestors; any multitexture
|
||||||
|
// operations will be accumulated from the indicated
|
||||||
|
// starting state.
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
void MultitexReducer::
|
||||||
|
scan(PandaNode *node, const RenderState *state, const TransformState *transform) {
|
||||||
|
CPT(RenderState) next_state = node->get_state()->compose(state);
|
||||||
|
CPT(TransformState) next_transform = node->get_transform()->compose(transform);
|
||||||
|
|
||||||
|
if (node->is_geom_node()) {
|
||||||
|
scan_geom_node(DCAST(GeomNode, node), next_state, next_transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
PandaNode::Children cr = node->get_children();
|
||||||
|
int num_children = cr.get_num_children();
|
||||||
|
for (int i = 0; i < num_children; i++) {
|
||||||
|
scan(cr.get_child(i), next_state, next_transform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// Function: MultitexReducer::set_target
|
||||||
|
// Access: Published
|
||||||
|
// Description: Specifies the target TextureStage (and TexCoordName)
|
||||||
|
// that will be left on each multitexture node after the
|
||||||
|
// flatten operation has completed.
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
void MultitexReducer::
|
||||||
|
set_target(TextureStage *stage) {
|
||||||
|
_target_stage = stage;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// Function: MultitexReducer::flatten
|
||||||
|
// Access: Published
|
||||||
|
// Description: Actually performs the reducing operations on the
|
||||||
|
// nodes that were previously scanned.
|
||||||
|
//
|
||||||
|
// A window that can be used to create texture buffers
|
||||||
|
// suitable for rendering this geometry must be
|
||||||
|
// supplied. This specifies the particular GSG that
|
||||||
|
// will be used to composite the textures.
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
void MultitexReducer::
|
||||||
|
flatten(GraphicsOutput *window) {
|
||||||
|
if (grutil_cat.is_debug()) {
|
||||||
|
grutil_cat.debug()
|
||||||
|
<< "Beginning flatten operation\n";
|
||||||
|
Stages::const_iterator mi;
|
||||||
|
for (mi = _stages.begin(); mi != _stages.end(); ++mi) {
|
||||||
|
const StageList &stage_list = (*mi).first;
|
||||||
|
const GeomList &geom_list = (*mi).second;
|
||||||
|
grutil_cat.debug(false)
|
||||||
|
<< "stage_list for:";
|
||||||
|
for (GeomList::const_iterator gi = geom_list.begin();
|
||||||
|
gi != geom_list.end();
|
||||||
|
++gi) {
|
||||||
|
const GeomInfo &geom_info = (*gi);
|
||||||
|
grutil_cat.debug(false)
|
||||||
|
<< " (" << geom_info._geom_node->get_name() << " g"
|
||||||
|
<< geom_info._index << ")";
|
||||||
|
}
|
||||||
|
grutil_cat.debug(false) << ":\n";
|
||||||
|
|
||||||
|
StageList::const_iterator si;
|
||||||
|
for (si = stage_list.begin(); si != stage_list.end(); ++si) {
|
||||||
|
const StageInfo &stage_info = (*si);
|
||||||
|
grutil_cat.debug(false)
|
||||||
|
<< " " << *stage_info._stage << " " << *stage_info._tex
|
||||||
|
<< " " << *stage_info._tex_mat << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Stages::const_iterator mi;
|
||||||
|
for (mi = _stages.begin(); mi != _stages.end(); ++mi) {
|
||||||
|
const StageList &stage_list = (*mi).first;
|
||||||
|
const GeomList &geom_list = (*mi).second;
|
||||||
|
|
||||||
|
// Create an offscreen buffer in which to render the new texture.
|
||||||
|
int x_size, y_size, aniso_degree;
|
||||||
|
Texture::FilterType minfilter, magfilter;
|
||||||
|
determine_size(x_size, y_size, aniso_degree,
|
||||||
|
minfilter, magfilter, stage_list);
|
||||||
|
|
||||||
|
GraphicsOutput *buffer =
|
||||||
|
window->make_texture_buffer("multitex", x_size, y_size);
|
||||||
|
buffer->set_one_shot(true);
|
||||||
|
Texture *tex = buffer->get_texture();
|
||||||
|
tex->set_anisotropic_degree(aniso_degree);
|
||||||
|
tex->set_minfilter(minfilter);
|
||||||
|
tex->set_magfilter(magfilter);
|
||||||
|
|
||||||
|
// Set up the offscreen buffer to render 0,0 to 1,1. This will be
|
||||||
|
// the whole texture, but nothing outside the texture.
|
||||||
|
GraphicsChannel *chan = buffer->get_channel(0);
|
||||||
|
GraphicsLayer *layer = chan->make_layer(0);
|
||||||
|
DisplayRegion *dr = layer->make_display_region();
|
||||||
|
PT(Camera) cam_node = new Camera("multitexCam");
|
||||||
|
PT(Lens) lens = new OrthographicLens();
|
||||||
|
lens->set_film_size(1.0f, 1.0f);
|
||||||
|
lens->set_film_offset(0.5f, 0.5f);
|
||||||
|
lens->set_near_far(-1000.0f, 1000.0f);
|
||||||
|
cam_node->set_lens(lens);
|
||||||
|
|
||||||
|
// Create a root node for the buffer's scene graph, and set up
|
||||||
|
// some appropriate properties for it.
|
||||||
|
NodePath render("buffer");
|
||||||
|
cam_node->set_scene(render);
|
||||||
|
render.set_bin("unsorted", 0);
|
||||||
|
render.set_depth_test(false);
|
||||||
|
render.set_depth_write(false);
|
||||||
|
|
||||||
|
NodePath cam = render.attach_new_node(cam_node);
|
||||||
|
dr->set_camera(cam);
|
||||||
|
|
||||||
|
// Put one plain white card in the background for the first
|
||||||
|
// texture layer to apply onto.
|
||||||
|
CardMaker cm("background");
|
||||||
|
cm.set_frame(0.0f, 1.0f, 0.0f, 1.0f);
|
||||||
|
render.attach_new_node(cm.generate());
|
||||||
|
|
||||||
|
StageList::const_iterator si;
|
||||||
|
for (si = stage_list.begin(); si != stage_list.end(); ++si) {
|
||||||
|
const StageInfo &stage_info = (*si);
|
||||||
|
|
||||||
|
make_texture_layer(render, stage_info, geom_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now modify the geometry to apply the new texture, instead of
|
||||||
|
// the old multitexture.
|
||||||
|
CPT(RenderAttrib) new_ta = DCAST(TextureAttrib, TextureAttrib::make())->
|
||||||
|
add_on_stage(_target_stage, tex);
|
||||||
|
|
||||||
|
GeomList::const_iterator gi;
|
||||||
|
for (gi = geom_list.begin(); gi != geom_list.end(); ++gi) {
|
||||||
|
const GeomInfo &geom_info = (*gi);
|
||||||
|
|
||||||
|
CPT(RenderState) geom_state =
|
||||||
|
geom_info._geom_node->get_geom_state(geom_info._index);
|
||||||
|
geom_state = geom_state->add_attrib(new_ta);
|
||||||
|
geom_info._geom_node->set_geom_state(geom_info._index, geom_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// Function: MultitexReducer::scan_geom_node
|
||||||
|
// Access: Private
|
||||||
|
// Description: Adds the Geoms in the indicated GeomNode to the
|
||||||
|
// internal database of multitexture elements.
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
void MultitexReducer::
|
||||||
|
scan_geom_node(GeomNode *node, const RenderState *state,
|
||||||
|
const TransformState *transform) {
|
||||||
|
if (grutil_cat.is_debug()) {
|
||||||
|
grutil_cat.debug()
|
||||||
|
<< "scan_geom_node(" << *node << ", " << *state << ", "
|
||||||
|
<< *transform << ")\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
int num_geoms = node->get_num_geoms();
|
||||||
|
for (int gi = 0; gi < num_geoms; gi++) {
|
||||||
|
CPT(RenderState) geom_net_state =
|
||||||
|
node->get_geom_state(gi)->compose(state);
|
||||||
|
|
||||||
|
// Get out the net TextureAttrib and TexMatrixAttrib from the state.
|
||||||
|
const RenderAttrib *attrib;
|
||||||
|
const TextureAttrib *ta = NULL;
|
||||||
|
|
||||||
|
attrib = geom_net_state->get_attrib(TextureAttrib::get_class_type());
|
||||||
|
if (attrib != (const RenderAttrib *)NULL) {
|
||||||
|
ta = DCAST(TextureAttrib, attrib);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ta != (TextureAttrib *)NULL && ta->get_num_on_stages() >= 2) {
|
||||||
|
// Ok, we have multitexture. Record the Geom.
|
||||||
|
CPT(TexMatrixAttrib) tma = DCAST(TexMatrixAttrib, TexMatrixAttrib::make());
|
||||||
|
attrib = geom_net_state->get_attrib(TexMatrixAttrib::get_class_type());
|
||||||
|
if (attrib != (const RenderAttrib *)NULL) {
|
||||||
|
tma = DCAST(TexMatrixAttrib, attrib);
|
||||||
|
}
|
||||||
|
|
||||||
|
StageList stage_list;
|
||||||
|
|
||||||
|
int num_stages = ta->get_num_on_stages();
|
||||||
|
for (int si = 0; si < num_stages; si++) {
|
||||||
|
TextureStage *stage = ta->get_on_stage(si);
|
||||||
|
|
||||||
|
stage_list.push_back(StageInfo(stage, ta, tma));
|
||||||
|
}
|
||||||
|
|
||||||
|
record_stage_list(stage_list, GeomInfo(node, gi));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// Function: MultitexReducer::record_stage_list
|
||||||
|
// Access: Private
|
||||||
|
// Description: Adds the record of this one Geom and its associated
|
||||||
|
// StageList.
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
void MultitexReducer::
|
||||||
|
record_stage_list(const MultitexReducer::StageList &stage_list,
|
||||||
|
const MultitexReducer::GeomInfo &geom_info) {
|
||||||
|
if (grutil_cat.is_debug()) {
|
||||||
|
grutil_cat.debug()
|
||||||
|
<< "record_stage_list for " << geom_info._geom_node->get_name() << " g"
|
||||||
|
<< geom_info._index << ":\n";
|
||||||
|
StageList::const_iterator si;
|
||||||
|
for (si = stage_list.begin(); si != stage_list.end(); ++si) {
|
||||||
|
const StageInfo &stage_info = (*si);
|
||||||
|
grutil_cat.debug(false)
|
||||||
|
<< " " << *stage_info._stage << " " << *stage_info._tex
|
||||||
|
<< " " << *stage_info._tex_mat << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_stages[stage_list].push_back(geom_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// Function: MultitexReducer::determine_size
|
||||||
|
// Access: Private
|
||||||
|
// Description: Tries to guess what size to make the new, collapsed
|
||||||
|
// texture based on the sizes of all of the textures
|
||||||
|
// used in the stage_list.
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
void MultitexReducer::
|
||||||
|
determine_size(int &x_size, int &y_size, int &aniso_degree,
|
||||||
|
Texture::FilterType &minfilter, Texture::FilterType &magfilter,
|
||||||
|
const MultitexReducer::StageList &stage_list) const {
|
||||||
|
x_size = 0;
|
||||||
|
y_size = 0;
|
||||||
|
aniso_degree = 0;
|
||||||
|
minfilter = Texture::FT_nearest;
|
||||||
|
magfilter = Texture::FT_nearest;
|
||||||
|
|
||||||
|
StageList::const_iterator si;
|
||||||
|
for (si = stage_list.begin(); si != stage_list.end(); ++si) {
|
||||||
|
const StageInfo &stage_info = (*si);
|
||||||
|
Texture *tex = stage_info._tex;
|
||||||
|
PixelBuffer *pbuffer = tex->_pbuffer;
|
||||||
|
|
||||||
|
if (stage_info._stage == _target_stage) {
|
||||||
|
// If we find the target stage, use that.
|
||||||
|
x_size = pbuffer->get_xsize();
|
||||||
|
y_size = pbuffer->get_ysize();
|
||||||
|
aniso_degree = tex->get_anisotropic_degree();
|
||||||
|
minfilter = tex->get_minfilter();
|
||||||
|
magfilter = tex->get_magfilter();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we never run across the target stage, just use the maximum
|
||||||
|
// of all encountered textures.
|
||||||
|
x_size = max(x_size, pbuffer->get_xsize());
|
||||||
|
y_size = max(y_size, pbuffer->get_ysize());
|
||||||
|
aniso_degree = max(aniso_degree, tex->get_anisotropic_degree());
|
||||||
|
minfilter = (Texture::FilterType)max((int)minfilter, (int)tex->get_minfilter());
|
||||||
|
magfilter = (Texture::FilterType)max((int)magfilter, (int)tex->get_magfilter());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// Function: MultitexReducer::make_texture_layer
|
||||||
|
// Access: Private
|
||||||
|
// Description: Creates geometry to render the texture into the
|
||||||
|
// offscreen buffer using the same effects that were
|
||||||
|
// requested by its multitexture specification.
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
void MultitexReducer::
|
||||||
|
make_texture_layer(const NodePath &render,
|
||||||
|
const MultitexReducer::StageInfo &stage_info,
|
||||||
|
const MultitexReducer::GeomList &geom_list) {
|
||||||
|
CPT(RenderAttrib) cba;
|
||||||
|
|
||||||
|
switch (stage_info._stage->get_mode()) {
|
||||||
|
case TextureStage::M_modulate:
|
||||||
|
cba = ColorBlendAttrib::make
|
||||||
|
(ColorBlendAttrib::M_add, ColorBlendAttrib::O_fbuffer_color,
|
||||||
|
ColorBlendAttrib::O_zero);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TextureStage::M_decal:
|
||||||
|
cba = ColorBlendAttrib::make
|
||||||
|
(ColorBlendAttrib::M_add, ColorBlendAttrib::O_incoming_alpha,
|
||||||
|
ColorBlendAttrib::O_one_minus_incoming_alpha);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TextureStage::M_blend:
|
||||||
|
cba = ColorBlendAttrib::make
|
||||||
|
(ColorBlendAttrib::M_add, ColorBlendAttrib::O_constant_color,
|
||||||
|
ColorBlendAttrib::O_one_minus_incoming_color,
|
||||||
|
stage_info._stage->get_color());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TextureStage::M_replace:
|
||||||
|
cba = ColorBlendAttrib::make_off();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TextureStage::M_add:
|
||||||
|
cba = ColorBlendAttrib::make
|
||||||
|
(ColorBlendAttrib::M_add, ColorBlendAttrib::O_one,
|
||||||
|
ColorBlendAttrib::O_one);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TextureStage::M_combine:
|
||||||
|
// We don't support the texture combiner here right now. For now,
|
||||||
|
// this is the same as modulate.
|
||||||
|
cba = ColorBlendAttrib::make
|
||||||
|
(ColorBlendAttrib::M_add, ColorBlendAttrib::O_fbuffer_color,
|
||||||
|
ColorBlendAttrib::O_zero);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
NodePath geom;
|
||||||
|
|
||||||
|
if (stage_info._stage->get_texcoord_name() == _target_stage->get_texcoord_name()) {
|
||||||
|
// If this TextureStage uses the target texcoords, we can just
|
||||||
|
// generate a simple card the fills the entire buffer.
|
||||||
|
CardMaker cm(stage_info._tex->get_name());
|
||||||
|
cm.set_uv_range(TexCoordf(0.0f, 0.0f), TexCoordf(1.0f, 1.0f));
|
||||||
|
cm.set_has_uvs(true);
|
||||||
|
cm.set_frame(0.0f, 1.0f, 0.0f, 1.0f);
|
||||||
|
|
||||||
|
geom = render.attach_new_node(cm.generate());
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// If this TextureStage uses some other texcoords, we have to
|
||||||
|
// generate geometry that maps the texcoords to the target space.
|
||||||
|
// This will work only for very simple cases where the geometry is
|
||||||
|
// not too extensive and doesn't repeat over the same UV's.
|
||||||
|
PT(GeomNode) geom_node = new GeomNode(stage_info._tex->get_name());
|
||||||
|
transfer_geom(geom_node, stage_info._stage->get_texcoord_name(),
|
||||||
|
geom_list);
|
||||||
|
|
||||||
|
geom = render.attach_new_node(geom_node);
|
||||||
|
|
||||||
|
// Make sure we override the vertex color, so we don't pollute the
|
||||||
|
// texture with geometry color.
|
||||||
|
geom.set_color(Colorf(1.0f, 1.0f, 1.0f, 1.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stage_info._tex_mat->is_identity()) {
|
||||||
|
geom.set_tex_transform(TextureStage::get_default(), stage_info._tex_mat);
|
||||||
|
}
|
||||||
|
|
||||||
|
geom.set_texture(stage_info._tex);
|
||||||
|
geom.node()->set_attrib(cba);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// Function: MultitexReducer::transfer_geom
|
||||||
|
// Access: Private
|
||||||
|
// Description: Copy the vertices from the indicated geom_list,
|
||||||
|
// mapping the vertex coordinates so that the geometry
|
||||||
|
// will render the appropriate distortion on the texture
|
||||||
|
// to map UV's from the specified set of texture
|
||||||
|
// coordinates to the target set.
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
void MultitexReducer::
|
||||||
|
transfer_geom(GeomNode *geom_node, const TexCoordName *texcoord_name,
|
||||||
|
const MultitexReducer::GeomList &geom_list) {
|
||||||
|
GeomList::const_iterator gi;
|
||||||
|
for (gi = geom_list.begin(); gi != geom_list.end(); ++gi) {
|
||||||
|
const GeomInfo &geom_info = (*gi);
|
||||||
|
|
||||||
|
PT(Geom) geom =
|
||||||
|
geom_info._geom_node->get_geom(geom_info._index)->make_copy();
|
||||||
|
|
||||||
|
PTA_Vertexf coords = PTA_Vertexf::empty_array(0);
|
||||||
|
PTA_TexCoordf texcoords = geom->get_texcoords_array(_target_stage->get_texcoord_name());
|
||||||
|
if (!texcoords.empty()) {
|
||||||
|
coords.reserve(texcoords.size());
|
||||||
|
for (size_t i = 0; i < texcoords.size(); i++) {
|
||||||
|
const TexCoordf &tc = texcoords[i];
|
||||||
|
Vertexf v(tc[0], 0.0f, tc[1]);
|
||||||
|
coords.push_back(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
geom->set_coords(coords, geom->get_texcoords_index(_target_stage->get_texcoord_name()));
|
||||||
|
geom->set_texcoords(TexCoordName::get_default(),
|
||||||
|
geom->get_texcoords_array(texcoord_name),
|
||||||
|
geom->get_texcoords_index(texcoord_name));
|
||||||
|
|
||||||
|
geom_node->add_geom(geom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// Function: MultitexReducer::StageInfo::Constructor
|
||||||
|
// Access: Public
|
||||||
|
// Description:
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
MultitexReducer::StageInfo::
|
||||||
|
StageInfo(TextureStage *stage, const TextureAttrib *ta,
|
||||||
|
const TexMatrixAttrib *tma) :
|
||||||
|
_stage(stage),
|
||||||
|
_tex_mat(TransformState::make_identity())
|
||||||
|
{
|
||||||
|
_tex = ta->get_on_texture(_stage);
|
||||||
|
if (tma->has_stage(stage)) {
|
||||||
|
_tex_mat = tma->get_transform(stage);
|
||||||
|
}
|
||||||
|
}
|
119
panda/src/grutil/multitexReducer.h
Normal file
119
panda/src/grutil/multitexReducer.h
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
// Filename: multitexReducer.h
|
||||||
|
// Created by: drose (30Nov04)
|
||||||
|
//
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// PANDA 3D SOFTWARE
|
||||||
|
// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved
|
||||||
|
//
|
||||||
|
// All use of this software is subject to the terms of the Panda 3d
|
||||||
|
// Software license. You should have received a copy of this license
|
||||||
|
// along with this source code; you will also find a current copy of
|
||||||
|
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
|
||||||
|
//
|
||||||
|
// To contact the maintainers of this program write to
|
||||||
|
// panda3d-general@lists.sourceforge.net .
|
||||||
|
//
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#ifndef MULTITEXREDUCER_H
|
||||||
|
#define MULTITEXREDUCER_H
|
||||||
|
|
||||||
|
#include "pandabase.h"
|
||||||
|
#include "texture.h"
|
||||||
|
#include "textureAttrib.h"
|
||||||
|
#include "textureStage.h"
|
||||||
|
#include "texMatrixAttrib.h"
|
||||||
|
#include "transformState.h"
|
||||||
|
#include "geomNode.h"
|
||||||
|
#include "luse.h"
|
||||||
|
#include "pointerTo.h"
|
||||||
|
#include "pmap.h"
|
||||||
|
#include "pvector.h"
|
||||||
|
|
||||||
|
class GraphicsOutput;
|
||||||
|
class PandaNode;
|
||||||
|
class RenderState;
|
||||||
|
class TransformState;
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////
|
||||||
|
// Class : MultitexReducer
|
||||||
|
// Description : This object presents an interface for generating new
|
||||||
|
// texture images that represent the combined images
|
||||||
|
// from one or more individual textures, reproducing
|
||||||
|
// certain kinds of multitexture effects without
|
||||||
|
// depending on multitexture support in the hardware.
|
||||||
|
//
|
||||||
|
// This also flattens out texture matrices and removes
|
||||||
|
// extra texture coordinates from the Geoms. It is thus
|
||||||
|
// not a complete substitute for true multitexturing,
|
||||||
|
// because it does not lend itself well to dynamic
|
||||||
|
// animation of the textures once they have been
|
||||||
|
// flattened. It is, however, useful for "baking in" a
|
||||||
|
// particular multitexture effect.
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
class EXPCL_PANDA MultitexReducer {
|
||||||
|
PUBLISHED:
|
||||||
|
MultitexReducer();
|
||||||
|
~MultitexReducer();
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
void scan(PandaNode *node, const RenderState *state,
|
||||||
|
const TransformState *transform);
|
||||||
|
|
||||||
|
void set_target(TextureStage *stage);
|
||||||
|
|
||||||
|
void flatten(GraphicsOutput *window);
|
||||||
|
|
||||||
|
private:
|
||||||
|
class StageInfo {
|
||||||
|
public:
|
||||||
|
StageInfo(TextureStage *stage, const TextureAttrib *ta,
|
||||||
|
const TexMatrixAttrib *tma);
|
||||||
|
|
||||||
|
INLINE bool operator < (const StageInfo &other) const;
|
||||||
|
|
||||||
|
PT(TextureStage) _stage;
|
||||||
|
PT(Texture) _tex;
|
||||||
|
CPT(TransformState) _tex_mat;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef pvector<StageInfo> StageList;
|
||||||
|
|
||||||
|
class GeomInfo {
|
||||||
|
public:
|
||||||
|
INLINE GeomInfo(GeomNode *geom_node, int index);
|
||||||
|
|
||||||
|
PT(GeomNode) _geom_node;
|
||||||
|
int _index;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef pvector<GeomInfo> GeomList;
|
||||||
|
|
||||||
|
typedef pmap<StageList, GeomList> Stages;
|
||||||
|
Stages _stages;
|
||||||
|
|
||||||
|
PT(TextureStage) _target_stage;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void scan_geom_node(GeomNode *node, const RenderState *state,
|
||||||
|
const TransformState *transform);
|
||||||
|
|
||||||
|
void record_stage_list(const StageList &stage_list,
|
||||||
|
const GeomInfo &geom_info);
|
||||||
|
|
||||||
|
void determine_size(int &x_size, int &y_size, int &aniso_degree,
|
||||||
|
Texture::FilterType &minfilter,
|
||||||
|
Texture::FilterType &magfilter,
|
||||||
|
const MultitexReducer::StageList &stage_list) const;
|
||||||
|
|
||||||
|
void make_texture_layer(const NodePath &render,
|
||||||
|
const StageInfo &stage_info,
|
||||||
|
const GeomList &geom_list);
|
||||||
|
void transfer_geom(GeomNode *geom_node, const TexCoordName *texcoord_name,
|
||||||
|
const MultitexReducer::GeomList &geom_list);
|
||||||
|
};
|
||||||
|
|
||||||
|
#include "multitexReducer.I"
|
||||||
|
|
||||||
|
#endif
|
@ -18,16 +18,11 @@
|
|||||||
|
|
||||||
#include "sceneGraphReducer.h"
|
#include "sceneGraphReducer.h"
|
||||||
#include "config_pgraph.h"
|
#include "config_pgraph.h"
|
||||||
#include "colorAttrib.h"
|
|
||||||
#include "texMatrixAttrib.h"
|
|
||||||
#include "colorScaleAttrib.h"
|
|
||||||
#include "accumulatedAttribs.h"
|
#include "accumulatedAttribs.h"
|
||||||
|
|
||||||
#include "geomNode.h"
|
|
||||||
#include "pointerTo.h"
|
#include "pointerTo.h"
|
||||||
#include "geom.h"
|
|
||||||
#include "indent.h"
|
|
||||||
#include "plist.h"
|
#include "plist.h"
|
||||||
|
#include "pmap.h"
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
// Function: SceneGraphReducer::flatten
|
// Function: SceneGraphReducer::flatten
|
||||||
|
@ -205,7 +205,7 @@ TransformState::
|
|||||||
bool TransformState::
|
bool TransformState::
|
||||||
operator < (const TransformState &other) const {
|
operator < (const TransformState &other) const {
|
||||||
static const int significant_flags =
|
static const int significant_flags =
|
||||||
(F_is_invalid | F_is_identity | F_components_given | F_hpr_given);
|
(F_is_invalid | F_is_identity | F_components_given | F_hpr_given | F_quat_given);
|
||||||
|
|
||||||
int flags = (_flags & significant_flags);
|
int flags = (_flags & significant_flags);
|
||||||
int other_flags = (other._flags & significant_flags);
|
int other_flags = (other._flags & significant_flags);
|
||||||
@ -219,8 +219,7 @@ operator < (const TransformState &other) const {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((_flags & (F_components_given | F_hpr_given | F_quat_given)) ==
|
if ((_flags & F_components_given) != 0) {
|
||||||
(F_components_given | F_hpr_given | F_quat_given)) {
|
|
||||||
// If the transform was specified componentwise, compare them
|
// If the transform was specified componentwise, compare them
|
||||||
// componentwise.
|
// componentwise.
|
||||||
int c = _pos.compare_to(other._pos);
|
int c = _pos.compare_to(other._pos);
|
||||||
|
@ -18,6 +18,8 @@
|
|||||||
|
|
||||||
#include "pandaFramework.h"
|
#include "pandaFramework.h"
|
||||||
#include "textNode.h"
|
#include "textNode.h"
|
||||||
|
#include "multitexReducer.h"
|
||||||
|
#include "configVariableBool.h"
|
||||||
|
|
||||||
#ifndef HAVE_GETOPT
|
#ifndef HAVE_GETOPT
|
||||||
#include "gnu_getopt.h"
|
#include "gnu_getopt.h"
|
||||||
@ -29,6 +31,11 @@
|
|||||||
|
|
||||||
PandaFramework framework;
|
PandaFramework framework;
|
||||||
|
|
||||||
|
ConfigVariableBool pview_test_hack
|
||||||
|
("pview-test-hack", false,
|
||||||
|
"Enable the '0' key in pview to run whatever hacky test happens to be in "
|
||||||
|
"there right now.");
|
||||||
|
|
||||||
void
|
void
|
||||||
event_W(CPT_Event, void *) {
|
event_W(CPT_Event, void *) {
|
||||||
// shift-W: open a new window on the same scene.
|
// shift-W: open a new window on the same scene.
|
||||||
@ -98,6 +105,19 @@ event_2(CPT_Event event, void *) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
event_0(CPT_Event event, void *) {
|
||||||
|
// 0: run hacky test.
|
||||||
|
MultitexReducer mr;
|
||||||
|
|
||||||
|
NodePath models = framework.get_models();
|
||||||
|
mr.scan(models.node(), models.get_net_state(), models.get_net_transform());
|
||||||
|
|
||||||
|
WindowFramework *wf = framework.get_window(0);
|
||||||
|
GraphicsWindow *win = wf->get_graphics_window();
|
||||||
|
mr.flatten(win);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
usage() {
|
usage() {
|
||||||
cerr <<
|
cerr <<
|
||||||
@ -210,6 +230,9 @@ main(int argc, char *argv[]) {
|
|||||||
framework.define_key("shift-w", "open a new window", event_W, NULL);
|
framework.define_key("shift-w", "open a new window", event_W, NULL);
|
||||||
framework.define_key("alt-enter", "toggle between window/fullscreen", event_Enter, NULL);
|
framework.define_key("alt-enter", "toggle between window/fullscreen", event_Enter, NULL);
|
||||||
framework.define_key("2", "split the window", event_2, NULL);
|
framework.define_key("2", "split the window", event_2, NULL);
|
||||||
|
if (pview_test_hack) {
|
||||||
|
framework.define_key("0", "run quick hacky test", event_0, NULL);
|
||||||
|
}
|
||||||
framework.main_loop();
|
framework.main_loop();
|
||||||
framework.report_frame_rate(nout);
|
framework.report_frame_rate(nout);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user