From 5ae4b0008b25072b5716f0cb585133315625a661 Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Sun, 28 Sep 2014 19:58:08 +0300 Subject: [PATCH] Rework ProgramData to do less unnecessary work Changes are now tracked per-uniform, allowing more updates to be skipped. The buffers used in a program are cached, making the no-changes case faster. Additionally, Program now creates a UniformBlockInfo struct for the default uniform. The program-wide uniform layout hash has been repurposed to cover the entire program. --- source/program.cpp | 21 +++- source/program.h | 2 +- source/programdata.cpp | 263 ++++++++++++++++++++++------------------- source/programdata.h | 62 ++++++---- 4 files changed, 198 insertions(+), 150 deletions(-) diff --git a/source/program.cpp b/source/program.cpp index a81d80ae..a4358acb 100644 --- a/source/program.cpp +++ b/source/program.cpp @@ -157,6 +157,8 @@ void Program::link() if(ARB_uniform_buffer_object) { + uniform_blocks.clear(); + std::set used_bind_points; count = get_program_i(id, GL_ACTIVE_UNIFORM_BLOCKS); for(unsigned i=0; i blockless_uniforms; + UniformBlockInfo &default_block = uniform_blocks[string()]; + default_block.data_size = 0; + default_block.bind_point = -1; + for(UniformMap::iterator i=uniforms.begin(); i!=uniforms.end(); ++i) if(!i->second.block) { i->second.location = glGetUniformLocation(id, i->second.name.c_str()); - blockless_uniforms.push_back(&i->second); + i->second.block = &default_block; + default_block.uniforms.push_back(&i->second); } - uniform_layout_hash = compute_layout_hash(blockless_uniforms); + default_block.layout_hash = compute_layout_hash(default_block.uniforms); + + string layout_descriptor; + for(UniformBlockMap::const_iterator i=uniform_blocks.begin(); i!=uniform_blocks.end(); ++i) + layout_descriptor += format("%d:%x\n", i->second.bind_point, i->second.layout_hash); + uniform_layout_hash = hash32(layout_descriptor); } Program::LayoutHash Program::compute_layout_hash(const vector &uniforms) @@ -285,14 +296,14 @@ int Program::get_uniform_location(const string &n) const add an offset. */ unsigned offset = lexical_cast(n.substr(open_bracket+1, n.size()-2-open_bracket)); i = uniforms.find(n.substr(0, open_bracket)); - if(i!=uniforms.end() && !i->second.block && offsetsecond.size) + if(i!=uniforms.end() && i->second.block->bind_point<0 && offsetsecond.size) return i->second.location+offset; } } return -1; } - return i->second.block ? -1 : i->second.location; + return i->second.block->bind_point<0 ? i->second.location : -1; } void Program::bind() const diff --git a/source/program.h b/source/program.h index 36f380a2..4feb862d 100644 --- a/source/program.h +++ b/source/program.h @@ -53,7 +53,7 @@ public: { std::string name; unsigned data_size; - unsigned bind_point; + int bind_point; std::vector uniforms; LayoutHash layout_hash; }; diff --git a/source/programdata.cpp b/source/programdata.cpp index 0beb9e7f..7705fac9 100644 --- a/source/programdata.cpp +++ b/source/programdata.cpp @@ -16,7 +16,7 @@ namespace GL { ProgramData::ProgramData(): last_block(0), buffer(0), - changes(NO_CHANGES) + dirty(0) { } // Blocks are intentionally left uncopied @@ -24,34 +24,36 @@ ProgramData::ProgramData(const ProgramData &other): uniforms(other.uniforms), last_block(0), buffer(0), - changes(NO_CHANGES) + dirty(0) { - for(UniformMap::iterator i=uniforms.begin(); i!=uniforms.end(); ++i) - i->second = i->second->clone(); + for(vector::iterator i=uniforms.begin(); i!=uniforms.end(); ++i) + *i = (*i)->clone(); } ProgramData &ProgramData::operator=(const ProgramData &other) { - for(UniformMap::iterator i=uniforms.begin(); i!=uniforms.end(); ++i) - delete i->second; + for(vector::iterator i=uniforms.begin(); i!=uniforms.end(); ++i) + delete *i; uniforms.clear(); - for(UniformMap::const_iterator i=other.uniforms.begin(); i!=other.uniforms.end(); ++i) - uniforms.insert(uniforms.end(), UniformMap::value_type(i->first, i->second->clone())); + for(vector::const_iterator i=other.uniforms.begin(); i!=other.uniforms.end(); ++i) + uniforms.push_back((*i)->clone()); for(BlockMap::iterator i=blocks.begin(); i!=blocks.end(); ++i) delete i->second.block; - blocks.clear(); + programs.clear(); - changes = NO_CHANGES; + last_block = 0; + buffer = 0; + dirty = 0; return *this; } ProgramData::~ProgramData() { - for(UniformMap::iterator i=uniforms.begin(); i!=uniforms.end(); ++i) - delete i->second; + for(vector::iterator i=uniforms.begin(); i!=uniforms.end(); ++i) + delete *i; for(BlockMap::iterator i=blocks.begin(); i!=blocks.end(); ++i) delete i->second.block; delete buffer; @@ -59,20 +61,26 @@ ProgramData::~ProgramData() void ProgramData::uniform(const string &name, Uniform *uni) { - UniformMap::iterator i = uniforms.find(name); - if(i!=uniforms.end()) + SlotMap::iterator i = uniform_slots.find(name); + if(i!=uniform_slots.end()) { - /* UniformBlock does not copy the uniforms, so existing blocks will be - left with stale pointers. This is not a problem as long as no one stores - pointers to the blocks and expects them to stay valid. */ - delete i->second; - i->second = uni; - changes = VALUES_CHANGED; + Uniform *&slot = uniforms[i->second]; + /* UniformBlock does not copy the uniforms, so existing default blocks + will be left with stale pointers. This is not a problem as long as no + one stores pointers to the blocks and expects them to stay valid. */ + delete slot; + slot = uni; + + if(i->secondsecond; + else // Force a full update if the mask isn't wide enough + dirty = ALL_ONES; } else { - uniforms[name] = uni; - changes = KEYS_CHANGED; + uniform_slots[name] = uniforms.size(); + uniforms.push_back(uni); + dirty = ALL_ONES; } } @@ -179,149 +187,158 @@ void ProgramData::uniform_matrix4_array(const string &name, unsigned n, const fl uniform(name, new UniformArray(n, v)); } -void ProgramData::find_uniforms_for_block(Block &block, const Program::UniformBlockInfo &info) const +unsigned ProgramData::compute_slot_mask(const Program::UniformBlockInfo &block) const { - block.uniforms.clear(); - for(vector::const_iterator i=info.uniforms.begin(); i!=info.uniforms.end(); ++i) + unsigned mask = 0; + for(vector::const_iterator i=block.uniforms.begin(); i!=block.uniforms.end(); ++i) { - // XXX individual array elements - UniformMap::const_iterator j = uniforms.find((*i)->name); - if(j!=uniforms.end()) - block.uniforms[(*i)->location] = &j->second; + SlotMap::const_iterator j = uniform_slots.find((*i)->name); + if(j!=uniform_slots.end() && j->secondsecond; } + + return mask; } -UniformBlock *ProgramData::create_block(const Program::UniformBlockInfo &info) const +void ProgramData::update_block(UniformBlock &block, const Program::UniformBlockInfo &info) const { - UniformBlock *block = new UniformBlock(info.data_size); - if(!buffer) + for(vector::const_iterator i=info.uniforms.begin(); i!=info.uniforms.end(); ++i) { - buffer = new Buffer(UNIFORM_BUFFER); - buffer->set_usage(STREAM_DRAW); + SlotMap::const_iterator j = uniform_slots.find((*i)->name); + if(j!=uniform_slots.end()) + block.attach(**i, *uniforms[j->second]); } - block->use_buffer(buffer, last_block); - last_block = block; - return block; } -const UniformBlock *ProgramData::get_block(const Program &prog, const Program::UniformBlockInfo *info) const +ProgramData::SharedBlock *ProgramData::get_shared_block(const Program::UniformBlockInfo &info) const { - if(changes) - { - for(BlockMap::iterator i=blocks.begin(); i!=blocks.end(); ++i) - if(i->second.changessecond.changes = changes; - changes = NO_CHANGES; - } - - Program::LayoutHash layout_hash = (info ? info->layout_hash : prog.get_uniform_layout_hash()); - - map::iterator i = blocks.find(layout_hash); + BlockMap::iterator i = blocks.find(info.layout_hash); if(i==blocks.end()) { - i = blocks.insert(BlockMap::value_type(layout_hash, Block())).first; - if(info) - { - find_uniforms_for_block(i->second, *info); + unsigned used = compute_slot_mask(info); + if(!used) + return 0; - if(!i->second.uniforms.empty()) + UniformBlock *block; + if(info.bind_point>=0) + { + if(!buffer) { - i->second.block = create_block(*info); - i->second.changes = VALUES_CHANGED; + buffer = new Buffer(UNIFORM_BUFFER); + buffer->set_usage(STREAM_DRAW); } + + block = new UniformBlock(info.data_size); + block->use_buffer(buffer, last_block); + last_block = block; } else - { - i->second.block = new UniformBlock; - i->second.changes = VALUES_CHANGED; - } + block = new UniformBlock; + + i = blocks.insert(BlockMap::value_type(info.layout_hash, SharedBlock(used, block))).first; } - else if(info && i->second.changes==KEYS_CHANGED) + + return &i->second; +} + +void ProgramData::apply() const +{ + const Program *prog = Program::current(); + if(!prog) + throw invalid_operation("ProgramData::apply"); + + Program::LayoutHash layout = prog->get_uniform_layout_hash(); + ProgramUniforms &pu = programs[layout]; + + if((dirty&pu.used)|pu.dirty) { - find_uniforms_for_block(i->second, *info); - if(!i->second.uniforms.empty()) + /* If the global dirty flag affects this program, add it to per-program + dirty flags and clear the global flag. A previously unseen program will + always cause this to happen. */ + if(dirty&pu.used) { - if(!i->second.block) - i->second.block = create_block(*info); - i->second.changes = VALUES_CHANGED; + Mask force_dirty = (dirty==ALL_ONES ? ALL_ONES : 0U); + for(BlockMap::iterator i=blocks.begin(); i!=blocks.end(); ++i) + i->second.dirty |= (dirty&i->second.used) | force_dirty; + for(ProgramMap::iterator i=programs.begin(); i!=programs.end(); ++i) + i->second.dirty |= (dirty&i->second.used) | force_dirty; + dirty = 0; } - else - i->second.changes = NO_CHANGES; - } - if(!i->second.block) - return 0; + const Program::UniformBlockMap &prog_blocks = prog->get_uniform_blocks(); - UniformBlock &block = *i->second.block; - if(i->second.changes) - { - if(info) + if(pu.dirty==ALL_ONES) { - vector::const_iterator j = info->uniforms.begin(); - map::const_iterator k = i->second.uniforms.begin(); - while(j!=info->uniforms.end() && k!=i->second.uniforms.end()) + /* The set of uniforms has changed since this program was last used. + Regenerate the list of uniform blocks. */ + pu.blocks.clear(); + pu.blocks.reserve(prog_blocks.size()); + + pu.used = 0; + for(Program::UniformBlockMap::const_iterator i=prog_blocks.begin(); i!=prog_blocks.end(); ++i) { - if(k->first==(*j)->location) + SharedBlock *shared = get_shared_block(i->second); + if(shared) { - block.attach(**j, **k->second); - ++k; + if(shared->dirty==ALL_ONES) + shared->used = compute_slot_mask(i->second); + pu.used |= shared->used; } - ++j; + + pu.blocks.push_back(ProgramBlock(i->second.bind_point, shared)); } } - else + + // Update the contents of all dirty blocks. + bool buffered_blocks_updated = false; + std::vector::iterator j = pu.blocks.begin(); + for(Program::UniformBlockMap::const_iterator i=prog_blocks.begin(); i!=prog_blocks.end(); ++i, ++j) { - for(UniformMap::const_iterator j=uniforms.begin(); j!=uniforms.end(); ++j) - { - int loc = prog.get_uniform_location(j->first); - if(loc>=0) - block.attach(loc, *j->second); - } + if(!j->shared || !j->shared->dirty) + continue; + + update_block(*j->block, i->second); + j->shared->dirty = 0; + buffered_blocks_updated |= (j->bind_point>=0); } - i->second.changes = NO_CHANGES; - } - return █ -} + pu.dirty = 0; -const UniformBlock *ProgramData::get_block(const Program &prog, const string &name) const -{ - if(name.empty()) - return get_block(prog, 0); - else - return get_block(prog, &prog.get_uniform_block_info(name)); + /* If any blocks stored in the buffer were updated, bind the buffer here + to avoid state thrashing. */ + if(buffered_blocks_updated) + buffer->bind(); + } + + for(vector::iterator i=pu.blocks.begin(); i!=pu.blocks.end(); ++i) + if(i->block) + i->block->apply(i->bind_point); } -void ProgramData::apply() const -{ - const Program *prog = Program::current(); - if(!prog) - throw invalid_operation("ProgramData::apply"); - const Program::UniformBlockMap &prog_blocks = prog->get_uniform_blocks(); - if(!prog_blocks.empty()) - { - typedef pair ApplyBlock; - list apply_blocks; - for(Program::UniformBlockMap::const_iterator i=prog_blocks.begin(); i!=prog_blocks.end(); ++i) - if(const UniformBlock *block = get_block(*prog, &i->second)) - apply_blocks.push_back(make_pair(block, i->second.bind_point)); +ProgramData::SharedBlock::SharedBlock(unsigned u, UniformBlock *b): + used(u), + dirty(u), + block(b) +{ } - if(buffer) - buffer->bind(); - for(list::const_iterator i=apply_blocks.begin(); i!=apply_blocks.end(); ++i) - i->first->apply(i->second); - } +ProgramData::ProgramBlock::ProgramBlock(): + bind_point(-1), + block(0), + shared(0) +{ } - if(const UniformBlock *block = get_block(*prog, 0)) - block->apply(-1); -} +ProgramData::ProgramBlock::ProgramBlock(int p, SharedBlock *b): + bind_point(p), + block(b ? b->block : 0), + shared(b) +{ } -ProgramData::Block::Block(): - changes(NO_CHANGES), - block(0) +ProgramData::ProgramUniforms::ProgramUniforms(): + used(ALL_ONES), + dirty(ALL_ONES) { } diff --git a/source/programdata.h b/source/programdata.h index 6f9242e6..0f752191 100644 --- a/source/programdata.h +++ b/source/programdata.h @@ -40,31 +40,55 @@ public: }; private: - enum Changes + typedef unsigned Mask; + + enum { - NO_CHANGES, - VALUES_CHANGED, - KEYS_CHANGED + MASK_BITS = sizeof(Mask)*8, + ALL_ONES = static_cast(-1) }; - struct Block + struct SharedBlock { - Changes changes; + Mask used; + Mask dirty; UniformBlock *block; - std::map uniforms; - Block(); + SharedBlock(); + SharedBlock(unsigned, UniformBlock *); }; - typedef std::map UniformMap; - typedef std::map BlockMap; + struct ProgramBlock + { + int bind_point; + UniformBlock *block; + SharedBlock *shared; + + ProgramBlock(); + ProgramBlock(int, SharedBlock *); + }; + + struct ProgramUniforms + { + std::vector blocks; + Mask used; + Mask dirty; + + ProgramUniforms(); + }; + + typedef std::map SlotMap; + typedef std::map BlockMap; + typedef std::map ProgramMap; // XXX All these mutables are a bit silly, but I'm out of better ideas - UniformMap uniforms; + SlotMap uniform_slots; + std::vector uniforms; mutable BlockMap blocks; + mutable ProgramMap programs; mutable UniformBlock *last_block; mutable Buffer *buffer; - mutable Changes changes; + mutable unsigned dirty; public: ProgramData(); @@ -97,17 +121,13 @@ public: void uniform_matrix4_array(const std::string &, unsigned, const float *); private: - void find_uniforms_for_block(Block &, const Program::UniformBlockInfo &) const; - UniformBlock *create_block(const Program::UniformBlockInfo &) const; - const UniformBlock *get_block(const Program &, const Program::UniformBlockInfo *) const; + unsigned compute_slot_mask(const Program::UniformBlockInfo &) const; + void update_block(UniformBlock &, const Program::UniformBlockInfo &) const; + SharedBlock *get_shared_block(const Program::UniformBlockInfo &) const; public: - /** Returns a UniformBlock matching the program's layout. If name is empty, - uniforms for the default uniform block (outside any uniform block - declarations) are returned. */ - const UniformBlock *get_block(const Program &prog, const std::string &name) const; - - /// Creates blocks for the currently bound program and applies them. + /** Applies uniform blocks for the currently bound program, creating them + if needed. */ void apply() const; }; -- 2.43.0