示例#1
0
void Video3DUberShader::create(std::set<std::string> const& material_names)
{
  UberShader::create(material_names);

  // create depth shader
  std::vector<ShaderProgramStage> warp_pass_stages;
  warp_pass_stages.push_back( ShaderProgramStage( scm::gl::STAGE_VERTEX_SHADER,          _warp_pass_vertex_shader()));
  warp_pass_stages.push_back( ShaderProgramStage( scm::gl::STAGE_GEOMETRY_SHADER,        _warp_pass_geometry_shader()));
  warp_pass_stages.push_back( ShaderProgramStage( scm::gl::STAGE_FRAGMENT_SHADER,        _warp_pass_fragment_shader()));

  auto warp_pass_program = std::make_shared<ShaderProgram>();
  warp_pass_program->set_shaders(warp_pass_stages);
  add_program(warp_pass_program);

  // create final shader
  std::vector<ShaderProgramStage> blend_pass_stages;
  blend_pass_stages.push_back(ShaderProgramStage(scm::gl::STAGE_VERTEX_SHADER, _blend_pass_vertex_shader()));
  blend_pass_stages.push_back(ShaderProgramStage(scm::gl::STAGE_FRAGMENT_SHADER, _blend_pass_fragment_shader()));

  auto blend_pass_program = std::make_shared<ShaderProgram>();
  blend_pass_program->set_shaders(blend_pass_stages);
  add_program(blend_pass_program);
}
示例#2
0
void LightingUberShader::
create(std::set<std::string> const& material_names,
       std::vector<LayerMapping const*> const& inputs) {

  fshader_factory_ = gua::make_unique<UberShaderFactory>(
    ShadingModel::LIGHTING_STAGE, material_names
    );

  fshader_factory_->add_inputs_to_main_functions(inputs, ShadingModel::GBUFFER_FRAGMENT_STAGE);

  UberShader::set_uniform_mapping(fshader_factory_->get_uniform_mapping());
  UberShader::set_output_mapping(fshader_factory_->get_output_mapping());

  // VERTEX SHADER -------------------------------------------------------------
  std::string vertex_shader(
    Resources::lookup_shader(Resources::shaders_uber_shaders_lighting_lighting_vert)
  );

  // FRAGMENT SHADER -----------------------------------------------------------
  std::string fragment_shader(
    Resources::lookup_shader(Resources::shaders_uber_shaders_lighting_lighting_frag)
  );

  // input from gbuffer
  std::stringstream s;
  for (unsigned i(0); i<inputs.size(); ++i)
    s << inputs[i]->get_gbuffer_input_definition(ShadingModel::StageID(i+1));

  string_utils::replace(fragment_shader, "@input_definition", s.str());

  // material specific uniforms
  string_utils::replace(fragment_shader, "@uniform_definition",
    get_uniform_mapping()->get_uniform_definition());

  // outputs
  string_utils::replace(fragment_shader, "@output_definition",
    get_gbuffer_mapping()->get_gbuffer_output_definition(false, false));

  // print material specific methods
  string_utils::replace(fragment_shader, "@material_methods",
    UberShader::print_material_methods(*fshader_factory_));

  // print main switch(es)
  string_utils::replace(fragment_shader, "@material_switch",
    UberShader::print_material_switch(*fshader_factory_));

  auto lighting_program = std::make_shared<ShaderProgram>();
  lighting_program->create_from_sources(vertex_shader, fragment_shader);
  add_program(lighting_program);
}
示例#3
0
void pipeline::reload_shadow_mapping() {
	auto vertex_file = shader_directory / "null.vertex.glsl";
	auto fragment_file = shader_directory / "null.fragment.glsl";
	if (!is_regular_file(vertex_file) || !is_regular_file(fragment_file)) return;

	shadow_mapping = basic_pass{
		"shadow_mapping",
		add_program(program::configuration()
			.vertex_shader(shader_directory / "null.vertex.glsl")
			.fragment_shader(shader_directory / "null.fragment.glsl")
			.preprocessor_commands("#version 330\n")),
		GL_DEPTH_BUFFER_BIT,
		GL_BACK,
		render_mode{}
			.set(render_mode::statics)
			.set(render_mode::dynamics)
			.set(render_mode::test_depth)};
}
示例#4
0
bool pipeline::import( path pipeline_file )
{
	BOOST_LOG_TRIVIAL(info) << "Importing pipeline file " << pipeline_file << "...";

	programs.clear();
	textures.clear();
	views.clear();
	buffers.clear();
	index_bound_buffers.clear();
	buffers_to_reset_pre_first_frame.clear();
	passes.clear();
	data_offsets = make_shared<std::vector<glm::uvec4>>();

	try {
		pipeline_file = canonical_and_preferred(pipeline_file, shader_directory);

		ptree root;
		read_json(pipeline_file.string(), root);
		
		static auto get_vec3 = [] ( const ptree& root ) {
			assert(3 == root.size());
			auto first = root.begin();
			auto x = (*first++).second.get<float>("");
			auto y = (*first++).second.get<float>("");
			auto z = (*first++).second.get<float>("");
			return glm::vec3(x, y, z);
		};



////////////////////////////////////////////////////////////////////////////////
/// Views
////////////////////////////////////////////////////////////////////////////////
		pass::view_container allocated_views;
		
		for (auto view_root : root.get_child("views") | boost::adaptors::map_values)
		{
			auto name = view_root.get<string>("name");
			auto width = view_root.get<int>("width");
			auto height = view_root.get<int>("height");

			// Reserved names
			static const auto reserved_names = {"user"};
			if (end(reserved_names) != boost::find(reserved_names, name))
				throw exception{("Name \"" + name + "\" is reserved.").c_str()};

			shared_ptr<view> view;

			// Orthographic
			if (auto orthographic_root = view_root.get_child_optional("orthographic")) {
				auto eye = get_vec3(view_root.get_child("eye"));
				auto target = get_vec3(view_root.get_child("target"));
				view = make_shared<black_label::rendering::view>(
					eye,
					target,
					glm::vec3{0.0f, 1.0f, 0.0f},
					width,
					height,
					orthographic_root->get<float>("left"),
					orthographic_root->get<float>("right"),
					orthographic_root->get<float>("bottom"),
					orthographic_root->get<float>("top"),
					orthographic_root->get<float>("near"),
					orthographic_root->get<float>("far")
				);
			}
			// Perspective
			else if (auto perspective_root = view_root.get_child_optional("perspective")) {
				auto eye = get_vec3(view_root.get_child("eye"));
				auto target = get_vec3(view_root.get_child("target"));
				view = make_shared<black_label::rendering::view>(
					eye,
					target,
					glm::vec3{0.0f, 1.0f, 0.0f},
					width,
					height,
					perspective_root->get<float>("near"),
					perspective_root->get<float>("far")
				);
			}
			// None
			else
				view = make_shared<black_label::rendering::view>(width, height);

			allocated_views.emplace_back(name, view);
			views.emplace(name, view);
		}



////////////////////////////////////////////////////////////////////////////////
/// LDM Views
////////////////////////////////////////////////////////////////////////////////
		ldm_view_count = 0;
		auto ldm_size = root.get<int>("ldm_size", 100);
		auto ldm_scale = root.get<float>("ldm_scale", 2000.0f);
		auto ldm_offset = get_vec3(root.get_child("ldm_offset"));
		for (auto view_root : root.get_child("ldm_views") | boost::adaptors::map_values)
		{
			auto eye = get_vec3(view_root) + ldm_offset;
			
			auto name = "ldm_view" + to_string(ldm_view_count);
			auto width = ldm_size;
			auto height = ldm_size;
			auto left = -ldm_scale;
			auto right = ldm_scale;
			auto top = ldm_scale;
			auto bottom = -ldm_scale;
			auto near_ = -ldm_scale;
			auto far_ = ldm_scale;

			auto view = make_shared<black_label::rendering::view>(
				eye,
				ldm_offset,
				glm::vec3{ 0.0f, 1.0f, 0.0f },
				width,
				height,
				left,
				right,
				bottom,
				top,
				near_,
				far_
				);

			allocated_views.emplace_back(name, view);
			views.emplace(name, view);

			++ldm_view_count;
		}



////////////////////////////////////////////////////////////////////////////////
/// Textures
////////////////////////////////////////////////////////////////////////////////
		pass::texture_container allocated_textures;

		for (auto child : root.get_child("textures"))
		{
			auto texture_ptree = child.second;
			auto name = texture_ptree.get<string>("name");
			auto format_name = texture_ptree.get<string>("format");
			auto filter_name = texture_ptree.get<string>("filter");
			auto wrap_name = texture_ptree.get<string>("wrap");
			auto data = texture_ptree.get_optional<string>("data");

			format::type format;
			if ("rgba8" == format_name) format = format::rgba8;
			else if ("rgba16f" == format_name) format = format::rgba16f;
			else if ("rgba32f" == format_name) format = format::rgba32f;
			else if ("depth16" == format_name) format = format::depth16;
			else if ("depth24" == format_name) format = format::depth24;
			else if ("depth32f" == format_name) format = format::depth32f;
			else throw exception{"Unknown format type."};
			
			filter::type filter;
			if ("nearest" == filter_name) filter = filter::nearest;
			else if ("linear" == filter_name) filter = filter::linear;
			else throw exception{"Unknown filter type."};

			wrap::type wrap;
			if ("clamp_to_edge" == wrap_name) wrap = wrap::clamp_to_edge;
			else if ("repeat" == wrap_name) wrap = wrap::repeat;
			else if ("mirrored_repeat" == wrap_name) wrap = wrap::mirrored_repeat;
			else throw exception{"Unknown wrap type."};

			auto width = texture_ptree.get<float>("width", 1.0);
			auto height = texture_ptree.get<float>("height", 1.0);

			auto texture = make_shared<gpu::storage_texture>(target::texture_2d, format, filter, wrap, width, height);

			if (data) {
				if ("random" == *data) {
					mt19937 random_number_generator;
					uniform_real_distribution<float> distribution(-1.0f, 1.0f);
					int random_texture_size = 800;
					auto random_data = vector<glm::vec3>(random_texture_size * random_texture_size);
					std::generate(random_data.begin(), random_data.end(), [&] () -> glm::vec3 { 
						glm::vec3 v;
						do 
						{
							v.x = distribution(random_number_generator);
							v.y = distribution(random_number_generator);
							v.z = distribution(random_number_generator);
						} while (glm::length(v) > 1.0f);
						return v * 0.5f + glm::vec3(0.5f);
					});
	
					texture->bind_and_update(random_texture_size, random_texture_size, random_data.data());
				} else throw exception{"Unknown data type."};
			}
			
			allocated_textures.emplace_back(name, texture);
			textures.emplace(name, texture);
		}



////////////////////////////////////////////////////////////////////////////////
/// Buffers
////////////////////////////////////////////////////////////////////////////////
		pass::buffer_container allocated_buffers;
		pass::index_bound_buffer_container allocated_index_bound_buffers;

		for (auto child : root.get_child("buffers"))
		{
			auto buffer_ptree = child.second;
			auto name = buffer_ptree.get<string>("name");
			auto target_name = buffer_ptree.get<string>("target");
			auto size = buffer_ptree.get<buffer::size_type>("size");
			auto binding = buffer_ptree.get_optional<buffer::size_type>("binding");
			auto data_name = buffer_ptree.get_optional<string>("data");
			auto reset_name = buffer_ptree.get_optional<string>("reset");

			target::type target;
			if ("shader_storage" == target_name) target = target::shader_storage;
			else if ("atomic_counter" == target_name) target = target::atomic_counter;
			else throw exception{"Unknown target type."};

			vector<uint8_t> data;
			if (data_name) {
				if ("null" == *data_name)
					data.resize(size);
				else throw exception{"Unknown data type."};
			}

			shared_ptr<gpu::buffer> buffer;
			if (binding) {
				auto index_bound_buffer = make_shared<gpu::index_bound_buffer>(target, usage::dynamic_copy, *binding, size, (data.empty()) ? nullptr : data.data());
				allocated_index_bound_buffers.emplace_back(name, index_bound_buffer);
				index_bound_buffers.emplace(name, index_bound_buffer);
				buffer = index_bound_buffer;
			} else {
				buffer = make_shared<gpu::buffer>(target, usage::dynamic_copy, size, (data.empty()) ? nullptr : data.data());
				allocated_buffers.emplace_back(name, buffer);
				buffers.emplace(name, buffer);
			}

			if (reset_name) {
				if (data.empty()) throw exception{"Reset is specified without data."};
				if ("pre_first_pass" == *reset_name)
					buffers_to_reset_pre_first_frame.emplace_back(move(buffer), move(data));
				else throw exception{"Unknown reset type."};
			}
		}



////////////////////////////////////////////////////////////////////////////////
/// Passes
////////////////////////////////////////////////////////////////////////////////
		for (auto pass_child : root.get_child("passes"))
		{
			auto pass_configuration = pass_child.second;
			auto name = pass_configuration.get<string>("name");

			if ("ldm_passes" == name) {
				program::configuration configuration;
				configuration.vertex_shader(shader_directory / "ldm.vertex.glsl");
				configuration.fragment_shader(shader_directory / "ldm.fragment.glsl");
				configuration.preprocessor_commands("#version 430\n");
				auto program_ = add_program(configuration);
				
				for (int id{0}; ldm_view_count > id; ++id) {
					const view* view = views.find("ldm_view" + to_string(id))->second.lock().get();

					pass::buffer_container pass_buffers;
					pass::index_bound_buffer_container pass_index_bound_buffers;

					pass_buffers.emplace_back("data_buffer", buffers.find("data_buffer")->second.lock());
					pass_index_bound_buffers.emplace_back("counter", index_bound_buffers.at("counter").lock());

					unsigned int clearing_mask{ 0u };
					unsigned int face_culling_mode{ 0u };

					render_mode render_mode;
					render_mode.set(render_mode::statics);
					render_mode.set(render_mode::dynamics);
					render_mode.set(render_mode::test_depth, false);
					render_mode.set(render_mode::materials, pass_configuration.get<bool>("materials", true));

					unsigned int post_memory_barrier_mask{GL_SHADER_STORAGE_BARRIER_BIT};
					auto preincrement_buffer_counter = 40000;

					pass::texture_container input_textures, output_textures;
					pass::view_container auxiliary_views;

					passes.emplace_back(
						"ldm_view" + to_string(id),
						program_,
						move(input_textures),
						move(output_textures),
						move(auxiliary_views),
						move(pass_buffers),
						move(pass_index_bound_buffers),
						clearing_mask,
						post_memory_barrier_mask,
						face_culling_mode,
						render_mode,
						view,
						user_view,
						preincrement_buffer_counter,
						0,
						data_offsets);
				}
				continue;
			}

			auto vertex_program = pass_configuration.get<string>("vertex_program");
			auto geometry_program = pass_configuration.get("geometry_program", "");
			auto fragment_program = pass_configuration.get<string>("fragment_program");

			string preprocessor_commands;
			for (const auto& preprocessor_command_child : pass_configuration.get_child("preprocessor_commands"))
			{
				const auto& preprocessor_command = preprocessor_command_child.second.get<string>("");
				preprocessor_commands += preprocessor_command + "\n";
			}		

			const view* view;
			auto view_name = pass_configuration.get<string>("view", "user");
			if ("user" == view_name)
				view = user_view;
			else {
				auto result = views.find(view_name);
				if (views.end() == result)
					throw exception{("View \"" + view_name + "\" is not defined.").c_str()};
				view = result->second.lock().get();
			}

			pass::view_container auxiliary_views;
			if (auto auxiliary_views_root = pass_configuration.get_child_optional("auxiliary_views"))
				for (const auto& auxiliary_view : *auxiliary_views_root | boost::adaptors::map_values)
				{
					auto name = auxiliary_view.get<string>("");
					if ("ldm_views" == name) {
						for (int id{0}; ldm_view_count > id; ++id) {
							auto ldm_name = "ldm_view" + to_string(id);
							auto view = views.at(ldm_name);
							auxiliary_views.emplace_back(move(ldm_name), view.lock());
						}
						continue;
					}

					auto view = views.at(name);
					auxiliary_views.emplace_back(move(name), view.lock());
				}

			pass::texture_container input_textures;
			if (auto input_texture_children = pass_configuration.get_child_optional("textures.input"))
				for (const auto& input_textures_child : *input_texture_children)
				{
					auto texture_name = input_textures_child.second.get<string>("");
					auto texture = textures.at(texture_name);
					input_textures.emplace_back(move(texture_name), texture.lock());
				}

			pass::texture_container output_textures;
			if (auto output_texture_children = pass_configuration.get_child_optional("textures.output"))
				for (const auto& output_textures_child : *output_texture_children)
				{
					auto texture_name = output_textures_child.second.get<string>("");
					auto texture = textures.at(texture_name);
					output_textures.emplace_back(move(texture_name), texture.lock());
				}

			pass::buffer_container pass_buffers;
			pass::index_bound_buffer_container pass_index_bound_buffers;
			if (auto buffer_children = pass_configuration.get_child_optional("buffers"))
				for (const auto& buffers_child : *buffer_children)
				{
					auto buffer_name = buffers_child.second.get<string>("");

					// First search through the regular buffers.
					auto buffer = buffers.find(buffer_name);
					if (buffers.cend() != buffer)
						pass_buffers.emplace_back(move(buffer_name), buffer->second.lock());
					// Otherwise, use the index-bound buffers.
					else
						pass_index_bound_buffers.emplace_back(move(buffer_name), index_bound_buffers.at(buffer_name).lock());					
				}

			render_mode render_mode;
			for (const auto& model_child : pass_configuration.get_child("models"))
			{
				const auto& model_value = model_child.second.get<string>("");
				if ("statics" == model_value) render_mode.set(render_mode::statics);
				else if ("dynamics" == model_value) render_mode.set(render_mode::dynamics);
				else if ("screen_aligned_quad" == model_value) render_mode.set(render_mode::screen_aligned_quad);
				else if ("photons" == model_value) render_mode.set(render_mode::photons);
				else throw exception{"Invalid model type."};
			}

			unsigned int clearing_mask{0u};
			if (auto clear_children = pass_configuration.get_child_optional("clear"))
				for (const auto& clear_child : *clear_children)
				{
					const auto& clear = clear_child.second.get<string>("");
					if ("depth" == clear) clearing_mask |= GL_DEPTH_BUFFER_BIT;
					else if ("color" == clear) clearing_mask |= GL_COLOR_BUFFER_BIT;
					else throw exception{"Unknown clear type."};
				}

			unsigned int post_memory_barrier_mask{0u};
			if (auto post_memory_barrier_children = pass_configuration.get_child_optional("memory_barrier.post"))
				for (const auto& post_memory_barrier_child : *post_memory_barrier_children)
				{
					const auto& post_memory_barrier = post_memory_barrier_child.second.get<string>("");
					if ("shader_storage" == post_memory_barrier) post_memory_barrier_mask |= GL_SHADER_STORAGE_BARRIER_BIT;
					else throw exception{"Unknown memory_barrier type."};
				}

			unsigned int face_culling_mode{0u};
			if (auto culling_children = pass_configuration.get_child_optional("culling"))
				for (const auto& culling_child : *culling_children)
				{
					const auto& culling = culling_child.second.get<string>("");
					if ("front_faces" == culling) face_culling_mode = (GL_BACK == face_culling_mode) ? GL_FRONT_AND_BACK : GL_FRONT;
					else if ("back_faces" == culling) face_culling_mode = (GL_FRONT == face_culling_mode) ? GL_FRONT_AND_BACK : GL_BACK;
					else throw exception{"Unknown culling type."};
				}

			// TODO: Generalize this feature.
			auto preincrement_buffer_counter = pass_configuration.get<int>("preincrement_buffer.counter", 0);

			render_mode.set(render_mode::test_depth, pass_configuration.get<bool>("test_depth", false));
			render_mode.set(render_mode::materials, pass_configuration.get<bool>("materials", true));

			program::configuration configuration;
			configuration.vertex_shader(shader_directory / vertex_program);
			if (!geometry_program.empty()) 
				configuration.geometry_shader(shader_directory / geometry_program);
			configuration.fragment_shader(shader_directory / fragment_program);
			configuration.preprocessor_commands(preprocessor_commands);
			for (const auto& input_textures_value : input_textures)
				configuration.add_vertex_attribute(input_textures_value.first);
			for (const auto& output_textures_value : output_textures)
				configuration.add_fragment_output(output_textures_value.first);

			auto program_ = add_program(configuration);

			passes.emplace_back(
				move(name),
				move(program_), 
				move(input_textures), 
				move(output_textures),
				move(auxiliary_views),
				move(pass_buffers),
				move(pass_index_bound_buffers),
				clearing_mask,
				post_memory_barrier_mask,
				face_culling_mode,
				render_mode,
				view,
				user_view,
				preincrement_buffer_counter,
				ldm_view_count,
				data_offsets);
		}
	} 
	catch (const exception& exception)
	{
		BOOST_LOG_TRIVIAL(warning) << "Error reading " << pipeline_file << ": " << exception.what(); 
		return complete = false;
	}

	reload_shadow_mapping();
	on_window_resized(user_view->window.x, user_view->window.y);

	BOOST_LOG_TRIVIAL(info) << "Imported pipeline file " << pipeline_file << ".";

	return complete = true;
}
示例#5
0
void ygen_emit::codegen_function( yssa_function* function )
{
    ygen_program* p = add_program( function );
    
    
    // Every program has one hidden parameter, the function object.
    assert( p->stackcount == 0 );
    p->stackcount += 1;
    
    
    // Extract all constants.
    for ( size_t i = 0; i < function->ops.size(); ++i )
    {
        yssa_opinst* op = function->ops.at( i );
        switch ( op->opcode )
        {
        case YL_GLOBAL:
        case YL_SETGLOBAL:
        case YL_KEY:
        case YL_SETKEY:
        case YL_DELKEY:
        {
            symkey k( op->key );
            auto i = p->strvals.find( k );
            if ( i != p->strvals.end() )
            {
                // Upgrade to key, in case value was used earlier as string.
                ygen_value& value = p->values.at( i->second );
                value.kind = YGEN_KEY;
                value.string->iskey = true;
            }
            else
            {
                size_t index = p->values.size();
                ygen_value value;
                value.kind = YGEN_KEY;
                value.string = add_string( k );
                value.string->iskey = true;
                p->values.push_back( value );
                p->strvals.emplace( k, index );
            }
            break;
        }
        
        case YL_NUMBER:
        {
            if ( ! p->numvals.count( op->number ) )
            {
                size_t index = p->values.size();
                ygen_value value;
                value.kind      = YGEN_NUMBER;
                value.number    = op->number;
                p->values.push_back( value );
                p->numvals.emplace( op->number, index );
            }
            break;
        }
        
        case YL_STRING:
        {
            symkey k( op->string->string, op->string->length );
            if ( ! p->strvals.count( k ) )
            {
                size_t index = p->values.size();
                ygen_value value;
                value.kind      = YGEN_STRING;
                value.string    = add_string( k );
                p->values.push_back( value );
                p->strvals.emplace( k, index );
            }
            break;
        }
        
        case YL_FUNCTION:
        {
            if ( ! p->funvals.count( op->function ) )
            {
                size_t index = p->values.size();
                ygen_value value;
                value.kind      = YGEN_PROGRAM;
                value.program   = add_program( op->function );
                p->values.push_back( value );
                p->funvals.emplace( op->function, index );
            }
            break;
        }
        
        default:
            break;
        }
    }
    
    
    // And sort the values.
    std::vector< size_t > sorted;
    sorted.reserve( p->values.size() );
    for ( size_t i = 0; i < p->values.size(); ++i )
    {
        sorted.push_back( i );
    }
    
    std::sort
    (
        sorted.begin(),
        sorted.end(),
        [=]( size_t a, size_t b )
        {
            const ygen_value& aval = p->values.at( a );
            const ygen_value& bval = p->values.at( b );
            
            if ( aval.kind < bval.kind )
                return true;
            if ( aval.kind != bval.kind )
                return false;
            
            switch ( aval.kind )
            {
            case YGEN_KEY:
            case YGEN_STRING:
                return strcmp( aval.string->text, bval.string->text ) < 0;
            
            case YGEN_NUMBER:
                return aval.number < bval.number;

            case YGEN_PROGRAM:
                return a <  b;
            }
            
        }
    );
    
    
    // Construct each block.
    std::vector< size_t > indexes;
    std::vector< std::pair< size_t, size_t > > jumps;
    for ( size_t i = 0; i < function->blocks.size(); ++i )
    {
        yssa_block* block = function->blocks.at( i ).get();
    
        // Compile each op in block.
        size_t count = 0;
        for ( size_t i = block->lstart; i < block->lfinal; i += count )
        {
            // Ops should be in sequential block order, with no gaps.
            assert( indexes.size() == i );
            
            // 'count' SSA ops map to one or more VM ops.
            size_t opindex = p->ops.size();
            count = opgen( p, i );
            indexes.insert( indexes.end(), count, opindex );
        }
        
        // Find next block.
        yssa_block* next_block = nullptr;
        if ( i + 1 < function->blocks.size() )
        {
            next_block = function->blocks.at( i + 1 ).get();
        }

        // Compile jumps.
        if ( block->test )
        {
            assert( block->next );
            assert( block->fail );
            
            assert( block->test->r != yl_opinst::NOVAL );
            unsigned r = block->test->r;
            yl_opcode jmpt;
            yl_opcode jmpf;
            
            if ( block->test->opcode != YSSA_ITEREACH )
            {
                jmpt = YL_JMPT;
                jmpf = YL_JMPF;
            }
            else
            {
                jmpt = YL_JMPV;
                jmpf = YL_JMPN;
            }
            
            if ( block->next == next_block )
            {
                size_t index = p->ops.size();
                p->ops.emplace_back( jmpf, r, (signed)0 );
                p->slocs.push_back( block->test->sloc );
                jumps.emplace_back( index, block->fail->lstart );
            }
            else if ( block->fail == next_block )
            {
                size_t index = p->ops.size();
                p->ops.emplace_back( jmpt, r, (signed)0 );
                p->slocs.push_back( block->test->sloc );
                jumps.emplace_back( index, block->next->lstart );
            }
            else
            {
                size_t index = p->ops.size();
                p->ops.emplace_back( jmpt, r, (signed)0 );
                p->slocs.push_back( block->test->sloc );
                jumps.emplace_back( index, block->next->lstart );
                index = p->ops.size();
                p->ops.emplace_back( YL_JMP, 0, (signed)0 );
                p->slocs.push_back( block->test->sloc );
                jumps.emplace_back( index, block->fail->lstart );
            }
        }
        else if ( block->next && block->next != next_block )
        {
            size_t index = p->ops.size();
            p->ops.emplace_back( YL_JMP, 0, (signed)0 );
            p->slocs.push_back( block->ops.size() ? block->ops.back()->sloc : -1 );
            jumps.emplace_back( index, block->next->lstart );
        }
    }
    
    indexes.push_back( p->ops.size() );
    
    
    // Fix up jumps.
    for ( auto jump : jumps )
    {
        size_t index = jump.first;
        size_t target = indexes.at( jump.second );
        signed j = (signed)target - (signed)( index + 1 );
        yl_opinst& op = p->ops.at( index );
        op = yl_opinst( op.opcode(), op.r(), j );
    }
    
    
    // Construct xframes.
    for ( size_t i = 0; i < function->blocks.size(); ++i )
    {
        yssa_block* block = function->blocks.at( i ).get();
        
        // Skip blocks without exception handlers.
        if ( ! block->xchandler )
        {
            continue;
        }
        
        // Construct xframe.
        yl_xframe xf;
        xf.start            = (unsigned)indexes.at( block->lstart );
        xf.end              = (unsigned)indexes.at( block->lfinal );
        xf.close_upvals     = block->xchandler->xclocalups;
        xf.close_iterators  = block->xchandler->xcitercount;
        xf.handler          = (unsigned)indexes.at( block->xchandler->lstart );
        
        // Check if we can just extend the previous xframe.
        if ( p->xframes.size()
                && p->xframes.back().end == xf.start
                && p->xframes.back().handler == xf.handler )
        {
            yl_xframe& merge = p->xframes.back();
            assert( merge.close_upvals == xf.close_upvals );
            assert( merge.close_iterators == xf.close_iterators );
            merge.end = xf.end;
            continue;
        }
        
        // Otherwise add it.
        p->xframes.push_back( xf );
    }
    
    
    // The first n debugvars are the names of upvals.
    for ( size_t i = 0; i < function->upnames.size(); ++i )
    {
        p->debugvars.push_back( nullptr );
    }
    

    // Construct debug information for variables.
    std::unordered_set< yssa_variable* > variables;
    for ( size_t i = 0; i < function->ops.size(); ++i )
    {
        yssa_opinst* op = function->ops.at( i );

        if ( op->has_associated() || ! op->variable )
        {
            continue;
        }
        
        yssa_variable* v = op->variable;
        if ( variables.count( v ) )
        {
            continue;
        }
        
        unsigned varindex = (unsigned)p->debugvars.size();
        p->debugvars.push_back( v );
        variables.insert( v );
        
        for ( yssa_live_range* live = v->live; live; live = live->next )
        {
            ygen_debugspan span;
            span.varindex   = varindex;
            span.start      = (unsigned)indexes.at( live->start );
            span.end        = (unsigned)indexes.at( live->final );
            
            if ( p->debugspans.size()
                    && p->debugspans.back().varindex == varindex
                    && p->debugspans.back().end == span.start )
            {
                p->debugspans.back().end = span.end;
                continue;
            }
            
            p->debugspans.push_back( span );
        }
    }
    
    std::sort
    (
        p->debugspans.begin(),
        p->debugspans.end(),
        []( const ygen_debugspan& a, const ygen_debugspan& b )
        {
            if ( a.start < b.start )
                return true;
            if ( a.start == b.start && a.end < b.end )
                return true;
            return false;
        }
    );
}