+#include <stdexcept>
+#include <msp/core/algorithm.h>
+#include <msp/gl/extensions/arb_direct_state_access.h>
+#include <msp/gl/extensions/arb_sampler_objects.h>
+#include <msp/gl/extensions/arb_shader_objects.h>
+#include <msp/gl/extensions/arb_uniform_buffer_object.h>
+#include <msp/gl/extensions/arb_vertex_array_object.h>
+#include "buffer.h"
+#include "deviceinfo.h"
+#include "pipelinestate.h"
+#include "program.h"
+#include "texture.h"
+#include "uniformblock.h"
+#include "vertexsetup.h"
+#include "windingtest.h"
+
+using namespace std;
+
+namespace Msp {
+namespace GL {
+
+const PipelineState *PipelineState::last_applied = 0;
+vector<int> PipelineState::bound_tex_targets;
+
+PipelineState::PipelineState():
+ shprog(0),
+ vertex_setup(0),
+ winding_test(0),
+ enabled_clip_planes(0),
+ changes(0)
+{
+ if(!ARB_direct_state_access && bound_tex_targets.empty())
+ bound_tex_targets.resize(Limits::get_global().max_texture_bindings);
+}
+
+PipelineState::~PipelineState()
+{
+ if(this==last_applied)
+ last_applied = 0;
+}
+
+void PipelineState::set_shader_program(const Program *p)
+{
+ if(p!=shprog)
+ {
+ shprog = p;
+ changes |= SHPROG;
+ }
+}
+
+void PipelineState::set_vertex_setup(const VertexSetup *s)
+{
+ if(s!=vertex_setup)
+ {
+ vertex_setup = s;
+ changes |= VERTEX_SETUP;
+ }
+}
+
+void PipelineState::set_winding_test(const WindingTest *w)
+{
+ if(w!=winding_test)
+ {
+ winding_test = w;
+ changes |= WINDING_TEST;
+ }
+}
+
+void PipelineState::set_enabled_clip_planes(unsigned p)
+{
+ if(p!=enabled_clip_planes)
+ {
+ enabled_clip_planes = p;
+ changes |= CLIP_PLANES;
+ }
+}
+
+void PipelineState::set_texture(unsigned binding, const Texture *tex, const Sampler *samp)
+{
+ if((tex!=0)!=(samp!=0))
+ throw invalid_argument("PipelineState::set_texture");
+
+ vector<BoundTexture>::iterator i = lower_bound_member(textures, binding, &BoundTexture::binding);
+ if(i==textures.end() || i->binding!=binding)
+ i = textures.insert(i, BoundTexture(binding));
+ if(tex!=i->texture || samp!=i->sampler)
+ {
+ i->texture = tex;
+ i->sampler = samp;
+ i->changed = true;
+ changes |= TEXTURES;
+ }
+}
+
+void PipelineState::set_uniforms(const DefaultUniformBlock *block)
+{
+ set_uniform_block_(-1, block);
+}
+
+void PipelineState::set_uniform_block(unsigned binding, const BufferBackedUniformBlock *block)
+{
+ set_uniform_block_(binding, block);
+}
+
+void PipelineState::set_uniform_block_(int binding, const UniformBlock *block)
+{
+ vector<BoundUniformBlock>::iterator i = lower_bound_member(uniform_blocks, binding, &BoundUniformBlock::binding);
+ if(i==uniform_blocks.end() || i->binding!=binding)
+ i = uniform_blocks.insert(i, BoundUniformBlock(binding));
+ if(block!=i->block || binding<0)
+ {
+ i->block = block;
+ i->changed = true;
+ changes |= UNIFORMS;
+ }
+}
+
+void PipelineState::apply() const
+{
+ apply(this==last_applied ? changes : ~0U);
+}
+
+void PipelineState::apply(unsigned mask) const
+{
+ if(mask&SHPROG)
+ glUseProgram(shprog ? shprog->get_id() : 0);
+
+ if(mask&VERTEX_SETUP)
+ {
+ glBindVertexArray(vertex_setup ? vertex_setup->get_id() : 0);
+ if(vertex_setup)
+ vertex_setup->refresh();
+ }
+
+ if(mask&WINDING_TEST)
+ {
+ if(winding_test)
+ {
+ glEnable(GL_CULL_FACE);
+ glFrontFace(winding_test->get_winding());
+ }
+ else
+ glDisable(GL_CULL_FACE);
+ }
+
+ if(mask&CLIP_PLANES)
+ {
+ unsigned max_clip_planes = Limits::get_global().max_clip_planes;
+ for(unsigned i=0; i<max_clip_planes; ++i)
+ {
+ if((enabled_clip_planes>>i)&1)
+ glEnable(GL_CLIP_PLANE0+i);
+ else
+ glDisable(GL_CLIP_PLANE0+i);
+ }
+ }
+
+ if(mask&TEXTURES)
+ {
+ if(last_applied && this!=last_applied)
+ {
+ vector<BoundTexture>::const_iterator i = textures.begin();
+ vector<BoundTexture>::const_iterator j = last_applied->textures.begin();
+ while(j!=last_applied->textures.end())
+ {
+ if(i==textures.end() || j->binding<i->binding)
+ {
+ if(bound_tex_targets[j->binding])
+ {
+ if(ARB_direct_state_access)
+ glBindTextureUnit(j->binding, 0);
+ else
+ {
+ glActiveTexture(GL_TEXTURE0+j->binding);
+ glBindTexture(bound_tex_targets[j->binding], 0);
+ }
+ }
+ ++j;
+ }
+ else
+ {
+ if(i->binding==j->binding)
+ ++j;
+ ++i;
+ }
+ }
+ }
+
+ for(vector<BoundTexture>::const_iterator i=textures.begin(); i!=textures.end(); ++i)
+ if(i->changed)
+ {
+ if(i->texture && i->sampler)
+ {
+ if(ARB_direct_state_access)
+ glBindTextureUnit(i->binding, i->texture->get_id());
+ else
+ {
+ glActiveTexture(GL_TEXTURE0+i->binding);
+ if(bound_tex_targets[i->binding] && static_cast<int>(i->texture->get_target())!=bound_tex_targets[i->binding])
+ glBindTexture(bound_tex_targets[i->binding], 0);
+ glBindTexture(i->texture->get_target(), i->texture->get_id());
+ bound_tex_targets[i->binding] = i->texture->get_target();
+ }
+
+ glBindSampler(i->binding, i->sampler->get_id());
+ i->sampler->refresh();
+ }
+ else if(bound_tex_targets[i->binding])
+ {
+ if(ARB_direct_state_access)
+ glBindTextureUnit(i->binding, 0);
+ else
+ {
+ glActiveTexture(GL_TEXTURE0+i->binding);
+ glBindTexture(bound_tex_targets[i->binding], 0);
+ bound_tex_targets[i->binding] = 0;
+ }
+ }
+
+ i->changed = false;
+ }
+ }
+
+ if(mask&UNIFORMS)
+ {
+ if(last_applied && this!=last_applied)
+ {
+ vector<BoundUniformBlock>::const_iterator i = uniform_blocks.begin();
+ vector<BoundUniformBlock>::const_iterator j = last_applied->uniform_blocks.begin();
+ while(j!=last_applied->uniform_blocks.end())
+ {
+ if(i==uniform_blocks.end() || j->binding<i->binding)
+ {
+ glBindBufferBase(GL_UNIFORM_BUFFER, j->binding, 0);
+ ++j;
+ }
+ else
+ {
+ if(i->binding==j->binding)
+ ++j;
+ ++i;
+ }
+ }
+ }
+
+ for(vector<BoundUniformBlock>::const_iterator i=uniform_blocks.begin(); i!=uniform_blocks.end(); ++i)
+ if(i->changed)
+ {
+ if(i->block)
+ {
+ if(i->binding>=0)
+ {
+ const BufferBackedUniformBlock *block = static_cast<const BufferBackedUniformBlock *>(i->block);
+ glBindBufferRange(GL_UNIFORM_BUFFER, i->binding, block->get_buffer()->get_id(), block->get_offset(), block->get_data_size());
+ }
+ else
+ static_cast<const DefaultUniformBlock *>(i->block)->apply();
+ }
+ else
+ glBindBufferBase(GL_UNIFORM_BUFFER, i->binding, 0);
+
+ i->changed = false;
+ }
+ }
+
+ last_applied = this;
+ changes &= ~mask;
+}
+
+void PipelineState::clear()
+{
+ if(last_applied)
+ {
+ glUseProgram(0);
+ glBindVertexArray(0);
+
+ unsigned max_clip_planes = Limits::get_global().max_clip_planes;
+ for(unsigned i=0; i<max_clip_planes; ++i)
+ if((last_applied->enabled_clip_planes>>i)&1)
+ glDisable(GL_CLIP_PLANE0+i);
+
+ for(vector<BoundTexture>::const_iterator i=last_applied->textures.begin(); i!=last_applied->textures.end(); ++i)
+ if(i->texture && i->sampler)
+ {
+ if(ARB_direct_state_access)
+ glBindTextureUnit(i->binding, 0);
+ else
+ {
+ glActiveTexture(GL_TEXTURE0+i->binding);
+ glBindTexture(bound_tex_targets[i->binding], 0);
+ bound_tex_targets[i->binding] = 0;
+ }
+ }
+
+ for(vector<BoundUniformBlock>::const_iterator i=last_applied->uniform_blocks.begin(); i!=last_applied->uniform_blocks.end(); ++i)
+ if(i->block)
+ glBindBufferBase(GL_UNIFORM_BUFFER, i->binding, 0);
+
+ last_applied = 0;
+ }
+}
+
+
+PipelineState::BoundTexture::BoundTexture(unsigned b):
+ binding(b),
+ changed(false),
+ texture(0),
+ sampler(0)
+{ }
+
+
+PipelineState::BoundUniformBlock::BoundUniformBlock(int b):
+ binding(b),
+ changed(false),
+ block(0)
+{ }
+
+} // namespace GL
+} // namespace Msp