1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-26 11:29:09 +00:00

Merge pull request #16 from TomHarte/UniformSetters

Implemented deferred setting of shader uniforms
This commit is contained in:
Thomas Harte 2016-05-15 15:22:06 -04:00
commit bf2932cd57
7 changed files with 265 additions and 72 deletions

View File

@ -39,7 +39,7 @@ void Machine::setup_output(float aspect_ratio)
"uint y = c & 14u;"
"uint iPhase = (c >> 4);"
"float phaseOffset = 6.283185308 * float(iPhase + 13u) / 14.0;"
"float phaseOffset = 6.283185308 * float(iPhase - 1u) / 13.0;"
"return (float(y) / 14.0) * (1.0 - amplitude) + step(1, iPhase) * amplitude * cos(phase + phaseOffset);"
"}");
_crt->set_output_device(Outputs::CRT::Television);
@ -55,8 +55,10 @@ void Machine::switch_region()
"uint y = c & 14u;"
"uint iPhase = (c >> 4);"
"float phaseOffset = (0.5 + 2.0 * (float(iPhase&1u) - 0.5) * (float((iPhase >> 1) + (iPhase&1u)) / 14.0));"
"return (float(y) / 14.0) * (1.0 - amplitude) + step(4, (iPhase + 2u) & 15u) * amplitude * cos(phase + 6.283185308 * phaseOffset);"
"uint direction = iPhase & 1u;"
"float phaseOffset = float(7u - direction) + (float(direction) - 0.5) * 2.0 * float(iPhase >> 1);"
"phaseOffset *= 6.283185308 / 12.0;"
"return (float(y) / 14.0) * (1.0 - amplitude) + step(4, (iPhase + 2u) & 15u) * amplitude * cos(phase + phaseOffset);"
"}");
_crt->set_new_timing(228, 312, Outputs::CRT::ColourSpace::YUV, 228, 1);
}
@ -171,6 +173,9 @@ void Machine::get_output_pixel(uint8_t *pixel, int offset)
}
// store colour
// static int lc;
// if(_vSyncEnabled) lc = 0; else lc += (offset == 159) ? 1 : 0;
// *pixel = (uint8_t)(((offset / 10) << 4) | (((lc >> 4)&7) << 1));
*pixel = outputColour;
}

View File

@ -89,15 +89,6 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_shader(const char *
std::unique_ptr<IntermediateShader> shader = std::unique_ptr<IntermediateShader>(new IntermediateShader(vertex_shader, fragment_shader, bindings));
free(vertex_shader);
shader->texIDUniform = shader->get_uniform_location("texID");
shader->outputTextureSizeUniform = shader->get_uniform_location("outputTextureSize");
shader->phaseCyclesPerTickUniform = shader->get_uniform_location("phaseCyclesPerTick");
shader->extensionUniform = shader->get_uniform_location("extension");
shader->weightsUniform = shader->get_uniform_location("weights");
shader->rgbToLumaChromaUniform = shader->get_uniform_location("rgbToLumaChroma");
shader->lumaChromaToRGBUniform = shader->get_uniform_location("lumaChromaToRGB");
shader->offsetsUniform = shader->get_uniform_location("offsets");
return shader;
}
@ -395,28 +386,24 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_filter_shader()
void IntermediateShader::set_output_size(unsigned int output_width, unsigned int output_height)
{
bind();
glUniform2i(outputTextureSizeUniform, (GLint)output_width, (GLint)output_height);
set_uniform("outputTextureSize", (GLint)output_width, (GLint)output_height);
}
void IntermediateShader::set_source_texture_unit(GLenum unit)
{
bind();
glUniform1i(texIDUniform, (GLint)(unit - GL_TEXTURE0));
set_uniform("texID", (GLint)(unit - GL_TEXTURE0));
}
void IntermediateShader::set_filter_coefficients(float sampling_rate, float cutoff_frequency)
{
bind();
// The process below: the source texture will have bilinear filtering enabled; so by
// sampling at non-integral offsets from the centre the shader can get a weighted sum
// of two source pixels, then scale that once, to do two taps per sample. However
// that works only if the two coefficients being joined have the same sign. So the
// number of usable taps is between 11 and 21 depending on the values that come out.
// Perform a linear search for the highest number of taps we can use with 11 samples.
float weights[12];
float offsets[5];
GLfloat weights[12];
GLfloat offsets[5];
unsigned int taps = 21;
while(1)
{
@ -458,26 +445,23 @@ void IntermediateShader::set_filter_coefficients(float sampling_rate, float cuto
taps -= 2;
}
glUniform4fv(weightsUniform, 3, weights);
glUniform1fv(offsetsUniform, 5, offsets);
set_uniform("weights", 4, 3, weights);
set_uniform("offsets", 1, 5, offsets);
}
void IntermediateShader::set_separation_frequency(float sampling_rate, float colour_burst_frequency)
{
// TODO: apply separately-formed filters for luminance and chrominance
set_filter_coefficients(sampling_rate, colour_burst_frequency);
}
void IntermediateShader::set_phase_cycles_per_sample(float phase_cycles_per_sample, bool extend_runs_to_full_cycle)
{
bind();
glUniform1f(phaseCyclesPerTickUniform, phase_cycles_per_sample);
glUniform1f(extensionUniform, extend_runs_to_full_cycle ? ceilf(1.0f / phase_cycles_per_sample) : 0.0f);
set_uniform("phaseCyclesPerTick", (GLfloat)phase_cycles_per_sample);
set_uniform("extension", extend_runs_to_full_cycle ? ceilf(1.0f / phase_cycles_per_sample) : 0.0f);
}
void IntermediateShader::set_colour_conversion_matrices(float *fromRGB, float *toRGB)
{
bind();
glUniformMatrix3fv(lumaChromaToRGBUniform, 1, GL_FALSE, toRGB);
glUniformMatrix3fv(rgbToLumaChromaUniform, 1, GL_FALSE, fromRGB);
set_uniform_matrix("lumaChromaToRGB", 3, false, toRGB);
set_uniform_matrix("rgbToLumaChroma", 3, false, fromRGB);
}

View File

@ -55,48 +55,41 @@ public:
static std::unique_ptr<IntermediateShader> make_rgb_filter_shader();
/*!
Binds this shader and configures it for output to an area of `output_width` and `output_height` pixels.
Queues the configuration of this shader for output to an area of `output_width` and `output_height` pixels
to occur upon the next `bind`.
*/
void set_output_size(unsigned int output_width, unsigned int output_height);
/*!
Binds this shader and sets the texture unit (as an enum, e.g. `GL_TEXTURE0`) to sample as source data.
Queues setting the texture unit (as an enum, e.g. `GL_TEXTURE0`) for source data to occur upon the next `bind`.
*/
void set_source_texture_unit(GLenum unit);
/*!
Binds this shader and sets filtering coefficients for a lowpass filter based on the cutoff.
Queues setting filtering coefficients for a lowpass filter based on the cutoff frequency to occur upon the next `bind`.
*/
void set_filter_coefficients(float sampling_rate, float cutoff_frequency);
/*!
Binds this shader and configures filtering to separate luminance and chrominance based on a colour
subcarrier of the given frequency.
Queues configuration of filtering to separate luminance and chrominance based on a colour
subcarrier of the given frequency to occur upon the next `bind`.
*/
void set_separation_frequency(float sampling_rate, float colour_burst_frequency);
/*!
Binds this shader and sets the number of colour phase cycles per sample, indicating whether output
geometry should be extended so that a complete colour cycle is included at both the beginning and end.
Queues setting of the number of colour phase cycles per sample, indicating whether output
geometry should be extended so that a complete colour cycle is included at both the beginning and end,
to occur upon the next `bind`.
*/
void set_phase_cycles_per_sample(float phase_cycles_per_sample, bool extend_runs_to_full_cycle);
/*!
Binds this shader and sets the matrices that convert between RGB and chrominance/luminance.
Queues setting the matrices that convert between RGB and chrominance/luminance to occur on the next `bind`.
*/
void set_colour_conversion_matrices(float *fromRGB, float *toRGB);
private:
static std::unique_ptr<IntermediateShader> make_shader(const char *fragment_shader, bool use_usampler, bool input_is_inputPosition);
GLint texIDUniform;
GLint outputTextureSizeUniform;
GLint weightsUniform;
GLint phaseCyclesPerTickUniform;
GLint extensionUniform;
GLint rgbToLumaChromaUniform;
GLint lumaChromaToRGBUniform;
GLint offsetsUniform;
};
}

View File

@ -86,45 +86,34 @@ std::unique_ptr<OutputShader> OutputShader::make_shader(const char *fragment_met
free(vertex_shader);
free(fragment_shader);
result->boundsSizeUniform = result->get_uniform_location("boundsSize");
result->boundsOriginUniform = result->get_uniform_location("boundsOrigin");
result->texIDUniform = result->get_uniform_location("texID");
result->scanNormalUniform = result->get_uniform_location("scanNormal");
result->positionConversionUniform = result->get_uniform_location("positionConversion");
return result;
}
void OutputShader::set_output_size(unsigned int output_width, unsigned int output_height, Outputs::CRT::Rect visible_area)
{
bind();
GLfloat outputAspectRatioMultiplier = ((float)output_width / (float)output_height) / (4.0f / 3.0f);
GLfloat bonusWidth = (outputAspectRatioMultiplier - 1.0f) * visible_area.size.width;
visible_area.origin.x -= bonusWidth * 0.5f * visible_area.size.width;
visible_area.size.width *= outputAspectRatioMultiplier;
glUniform2f(boundsOriginUniform, (GLfloat)visible_area.origin.x, (GLfloat)visible_area.origin.y);
glUniform2f(boundsSizeUniform, (GLfloat)visible_area.size.width, (GLfloat)visible_area.size.height);
set_uniform("boundsOrigin", (GLfloat)visible_area.origin.x, (GLfloat)visible_area.origin.y);
set_uniform("boundsSize", (GLfloat)visible_area.size.width, (GLfloat)visible_area.size.height);
}
void OutputShader::set_source_texture_unit(GLenum unit)
{
bind();
glUniform1i(texIDUniform, (GLint)(unit - GL_TEXTURE0));
set_uniform("texID", (GLint)(unit - GL_TEXTURE0));
}
void OutputShader::set_timing(unsigned int height_of_display, unsigned int cycles_per_line, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider)
{
bind();
float scan_angle = atan2f(1.0f / (float)height_of_display, 1.0f);
float scan_normal[] = { -sinf(scan_angle), cosf(scan_angle)};
float multiplier = (float)cycles_per_line / ((float)height_of_display * (float)horizontal_scan_period);
GLfloat scan_angle = atan2f(1.0f / (float)height_of_display, 1.0f);
GLfloat scan_normal[] = { -sinf(scan_angle), cosf(scan_angle)};
GLfloat multiplier = (float)cycles_per_line / ((float)height_of_display * (float)horizontal_scan_period);
scan_normal[0] *= multiplier;
scan_normal[1] *= multiplier;
glUniform2f(scanNormalUniform, scan_normal[0], scan_normal[1]);
glUniform2f(positionConversionUniform, horizontal_scan_period, vertical_scan_period / (unsigned int)vertical_period_divider);
set_uniform("scanNormal", scan_normal[0], scan_normal[1]);
set_uniform("positionConversion", (GLfloat)horizontal_scan_period, (GLfloat)vertical_scan_period / (GLfloat)vertical_period_divider);
}

View File

@ -42,23 +42,22 @@ public:
using Shader::Shader;
/*!
Binds this shader and configures it for output to an area of `output_width` and `output_height` pixels, ensuring
the largest possible drawing size that allows everything within `visible_area` to be visible.
Queues configuration for output to an area of `output_width` and `output_height` pixels, ensuring
the largest possible drawing size that allows everything within `visible_area` to be visible, to
occur upon the next `bind`.
*/
void set_output_size(unsigned int output_width, unsigned int output_height, Outputs::CRT::Rect visible_area);
/*!
Binds this shader and sets the texture unit (as an enum, e.g. `GL_TEXTURE0`) to sample as source data.
Queues setting of the texture unit (as an enum, e.g. `GL_TEXTURE0`) for source data upon the next `bind`.
*/
void set_source_texture_unit(GLenum unit);
/*!
Binds this shader and configures its understanding of how to map from the source vertex stream to screen coordinates.
Queues configuring this shader's understanding of how to map from the source vertex stream to screen coordinates,
to occur upon the next `bind`.
*/
void set_timing(unsigned int height_of_display, unsigned int cycles_per_line, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider);
private:
GLint boundsOriginUniform, boundsSizeUniform, texIDUniform, scanNormalUniform, positionConversionUniform;
};
}

View File

@ -95,6 +95,7 @@ void Shader::bind()
glUseProgram(_shader_program);
bound_shader = this;
}
flush_functions();
}
void Shader::unbind()
@ -120,3 +121,188 @@ void Shader::enable_vertex_attribute_with_pointer(const char *name, GLint size,
glVertexAttribPointer((GLuint)location, size, type, normalised, stride, pointer);
glVertexAttribDivisor((GLuint)location, divisor);
}
// The various set_uniforms...
#define location() glGetUniformLocation(_shader_program, name.c_str())
void Shader::set_uniform(const std::string &name, GLint value)
{
enqueue_function([name, value, this] {
glUniform1i(location(), value);
});
}
void Shader::set_uniform(const std::string &name, GLuint value)
{
enqueue_function([name, value, this] {
glUniform1ui(location(), value);
});
}
void Shader::set_uniform(const std::string &name, GLfloat value)
{
enqueue_function([name, value, this] {
glUniform1f(location(), value);
});
}
void Shader::set_uniform(const std::string &name, GLint value1, GLint value2)
{
enqueue_function([name, value1, value2, this] {
glUniform2i(location(), value1, value2);
});
}
void Shader::set_uniform(const std::string &name, GLfloat value1, GLfloat value2)
{
enqueue_function([name, value1, value2, this] {
glUniform2f(location(), value1, value2);
});
}
void Shader::set_uniform(const std::string &name, GLuint value1, GLuint value2)
{
enqueue_function([name, value1, value2, this] {
glUniform2ui(location(), value1, value2);
});
}
void Shader::set_uniform(const std::string &name, GLint value1, GLint value2, GLint value3)
{
enqueue_function([name, value1, value2, value3, this] {
glUniform3i(location(), value1, value2, value3);
});
}
void Shader::set_uniform(const std::string &name, GLfloat value1, GLfloat value2, GLfloat value3)
{
enqueue_function([name, value1, value2, value3, this] {
glUniform3f(location(), value1, value2, value3);
});
}
void Shader::set_uniform(const std::string &name, GLuint value1, GLuint value2, GLuint value3)
{
enqueue_function([name, value1, value2, value3, this] {
glUniform3ui(location(), value1, value2, value3);
});
}
void Shader::set_uniform(const std::string &name, GLint value1, GLint value2, GLint value3, GLint value4)
{
enqueue_function([name, value1, value2, value3, value4, this] {
glUniform4i(location(), value1, value2, value3, value4);
});
}
void Shader::set_uniform(const std::string &name, GLfloat value1, GLfloat value2, GLfloat value3, GLfloat value4)
{
enqueue_function([name, value1, value2, value3, value4, this] {
glUniform4f(location(), value1, value2, value3, value4);
});
}
void Shader::set_uniform(const std::string &name, GLuint value1, GLuint value2, GLuint value3, GLuint value4)
{
enqueue_function([name, value1, value2, value3, value4, this] {
glUniform4ui(location(), value1, value2, value3, value4);
});
}
void Shader::set_uniform(const std::string &name, GLint size, GLsizei count, const GLint *values)
{
size_t number_of_values = (size_t)count * (size_t)size;
GLint *values_copy = new GLint[number_of_values];
memcpy(values_copy, values, sizeof(*values) * (size_t)number_of_values);
enqueue_function([name, size, count, values_copy, this] {
switch(size)
{
case 1: glUniform1iv(location(), count, values_copy); break;
case 2: glUniform2iv(location(), count, values_copy); break;
case 3: glUniform3iv(location(), count, values_copy); break;
case 4: glUniform4iv(location(), count, values_copy); break;
}
delete[] values_copy;
});
}
void Shader::set_uniform(const std::string &name, GLint size, GLsizei count, const GLfloat *values)
{
size_t number_of_values = (size_t)count * (size_t)size;
GLfloat *values_copy = new GLfloat[number_of_values];
memcpy(values_copy, values, sizeof(*values) * (size_t)number_of_values);
enqueue_function([name, size, count, values_copy, this] {
switch(size)
{
case 1: glUniform1fv(location(), count, values_copy); break;
case 2: glUniform2fv(location(), count, values_copy); break;
case 3: glUniform3fv(location(), count, values_copy); break;
case 4: glUniform4fv(location(), count, values_copy); break;
}
delete[] values_copy;
});
}
void Shader::set_uniform(const std::string &name, GLint size, GLsizei count, const GLuint *values)
{
size_t number_of_values = (size_t)count * (size_t)size;
GLuint *values_copy = new GLuint[number_of_values];
memcpy(values_copy, values, sizeof(*values) * (size_t)number_of_values);
enqueue_function([name, size, count, values_copy, this] {
switch(size)
{
case 1: glUniform1uiv(location(), count, values_copy); break;
case 2: glUniform2uiv(location(), count, values_copy); break;
case 3: glUniform3uiv(location(), count, values_copy); break;
case 4: glUniform4uiv(location(), count, values_copy); break;
}
delete[] values_copy;
});
}
void Shader::set_uniform_matrix(const std::string &name, GLint size, bool transpose, const GLfloat *values)
{
set_uniform_matrix(name, size, 1, transpose, values);
}
void Shader::set_uniform_matrix(const std::string &name, GLint size, GLsizei count, bool transpose, const GLfloat *values)
{
size_t number_of_values = (size_t)count * (size_t)size * (size_t)size;
GLfloat *values_copy = new GLfloat[number_of_values];
memcpy(values_copy, values, sizeof(*values) * number_of_values);
enqueue_function([name, size, count, transpose, values_copy, this] {
GLboolean glTranspose = transpose ? GL_TRUE : GL_FALSE;
switch(size)
{
case 2: glUniformMatrix2fv(location(), count, glTranspose, values_copy); break;
case 3: glUniformMatrix3fv(location(), count, glTranspose, values_copy); break;
case 4: glUniformMatrix4fv(location(), count, glTranspose, values_copy); break;
}
delete[] values_copy;
});
}
void Shader::enqueue_function(std::function<void(void)> function)
{
_function_mutex.lock();
_enqueued_functions.push_back(function);
_function_mutex.unlock();
}
void Shader::flush_functions()
{
_function_mutex.lock();
for(std::function<void(void)> function : _enqueued_functions)
{
function();
}
_enqueued_functions.clear();
_function_mutex.unlock();
}

View File

@ -10,6 +10,10 @@
#define Shader_hpp
#include "OpenGL.hpp"
#include <string>
#include <functional>
#include <list>
#include <mutex>
namespace OpenGL {
@ -44,6 +48,8 @@ public:
Performs an @c glUseProgram to make this the active shader unless:
(i) it was the previous shader bound; and
(ii) no calls have been received to unbind in the interim.
Subsequently performs all work queued up for the next bind irrespective of whether a @c glUseProgram call occurred.
*/
void bind();
@ -75,9 +81,40 @@ public:
*/
void enable_vertex_attribute_with_pointer(const char *name, GLint size, GLenum type, GLboolean normalised, GLsizei stride, const GLvoid *pointer, GLuint divisor);
/*!
All @c set_uniforms queue up the requested uniform changes. Changes are applied automatically the next time the shader is bound.
*/
void set_uniform(const std::string &name, GLint value);
void set_uniform(const std::string &name, GLint value1, GLint value2);
void set_uniform(const std::string &name, GLint value1, GLint value2, GLint value3);
void set_uniform(const std::string &name, GLint value1, GLint value2, GLint value3, GLint value4);
void set_uniform(const std::string &name, GLint size, GLsizei count, const GLint *values);
void set_uniform(const std::string &name, GLfloat value);
void set_uniform(const std::string &name, GLfloat value1, GLfloat value2);
void set_uniform(const std::string &name, GLfloat value1, GLfloat value2, GLfloat value3);
void set_uniform(const std::string &name, GLfloat value1, GLfloat value2, GLfloat value3, GLfloat value4);
void set_uniform(const std::string &name, GLint size, GLsizei count, const GLfloat *values);
void set_uniform(const std::string &name, GLuint value);
void set_uniform(const std::string &name, GLuint value1, GLuint value2);
void set_uniform(const std::string &name, GLuint value1, GLuint value2, GLuint value3);
void set_uniform(const std::string &name, GLuint value1, GLuint value2, GLuint value3, GLuint value4);
void set_uniform(const std::string &name, GLint size, GLsizei count, const GLuint *values);
void set_uniform_matrix(const std::string &name, GLint size, bool transpose, const GLfloat *values);
void set_uniform_matrix(const std::string &name, GLint size, GLsizei count, bool transpose, const GLfloat *values);
private:
GLuint compile_shader(const char *source, GLenum type);
GLuint _shader_program;
void flush_functions();
std::list<std::function<void(void)>> _enqueued_functions;
std::mutex _function_mutex;
protected:
void enqueue_function(std::function<void(void)> function);
};
}