/* * Copyright (c) 2021, Jesse Buhagiar * Copyright (c) 2021, Stephan Unverwerth * Copyright (c) 2022, Jelle Raaijmakers * * SPDX-License-Identifier: BSD-2-Clause */ #include namespace GL { template void GLContext::get_light_param(GLenum light, GLenum pname, T* params) { auto const& light_state = m_light_states[light - GL_LIGHT0]; switch (pname) { case GL_AMBIENT: params[0] = light_state.ambient_intensity.x(); params[1] = light_state.ambient_intensity.y(); params[2] = light_state.ambient_intensity.z(); params[3] = light_state.ambient_intensity.w(); break; case GL_DIFFUSE: params[0] = light_state.diffuse_intensity.x(); params[1] = light_state.diffuse_intensity.y(); params[2] = light_state.diffuse_intensity.z(); params[3] = light_state.diffuse_intensity.w(); break; case GL_SPECULAR: params[0] = light_state.specular_intensity.x(); params[1] = light_state.specular_intensity.y(); params[2] = light_state.specular_intensity.z(); params[3] = light_state.specular_intensity.w(); break; case GL_SPOT_DIRECTION: params[0] = light_state.spotlight_direction.x(); params[1] = light_state.spotlight_direction.y(); params[2] = light_state.spotlight_direction.z(); break; case GL_SPOT_EXPONENT: *params = light_state.spotlight_exponent; break; case GL_SPOT_CUTOFF: *params = light_state.spotlight_cutoff_angle; break; case GL_CONSTANT_ATTENUATION: *params = light_state.constant_attenuation; break; case GL_LINEAR_ATTENUATION: *params = light_state.linear_attenuation; break; case GL_QUADRATIC_ATTENUATION: *params = light_state.quadratic_attenuation; break; } } template void GLContext::get_material_param(Face face, GLenum pname, T* params) { auto const& material = m_material_states[face]; switch (pname) { case GL_AMBIENT: params[0] = static_cast(material.ambient.x()); params[1] = static_cast(material.ambient.y()); params[2] = static_cast(material.ambient.z()); params[3] = static_cast(material.ambient.w()); break; case GL_DIFFUSE: params[0] = static_cast(material.diffuse.x()); params[1] = static_cast(material.diffuse.y()); params[2] = static_cast(material.diffuse.z()); params[3] = static_cast(material.diffuse.w()); break; case GL_SPECULAR: params[0] = static_cast(material.specular.x()); params[1] = static_cast(material.specular.y()); params[2] = static_cast(material.specular.z()); params[3] = static_cast(material.specular.w()); break; case GL_EMISSION: params[0] = static_cast(material.emissive.x()); params[1] = static_cast(material.emissive.y()); params[2] = static_cast(material.emissive.z()); params[3] = static_cast(material.emissive.w()); break; case GL_SHININESS: *params = material.shininess; break; } } void GLContext::gl_color_material(GLenum face, GLenum mode) { APPEND_TO_CALL_LIST_AND_RETURN_IF_NEEDED(gl_color_material, face, mode); RETURN_WITH_ERROR_IF(m_in_draw_state, GL_INVALID_OPERATION); RETURN_WITH_ERROR_IF(face != GL_FRONT && face != GL_BACK && face != GL_FRONT_AND_BACK, GL_INVALID_ENUM); RETURN_WITH_ERROR_IF(mode != GL_EMISSION && mode != GL_AMBIENT && mode != GL_DIFFUSE && mode != GL_SPECULAR && mode != GL_AMBIENT_AND_DIFFUSE, GL_INVALID_ENUM); m_color_material_face = face; m_color_material_mode = mode; m_light_state_is_dirty = true; } void GLContext::gl_get_light(GLenum light, GLenum pname, void* params, GLenum type) { APPEND_TO_CALL_LIST_AND_RETURN_IF_NEEDED(gl_get_light, light, pname, params, type); RETURN_WITH_ERROR_IF(m_in_draw_state, GL_INVALID_OPERATION); RETURN_WITH_ERROR_IF(light < GL_LIGHT0 || light > GL_LIGHT0 + m_device_info.num_lights, GL_INVALID_ENUM); RETURN_WITH_ERROR_IF(!(pname == GL_AMBIENT || pname == GL_DIFFUSE || pname == GL_SPECULAR || pname == GL_SPOT_DIRECTION || pname == GL_SPOT_EXPONENT || pname == GL_SPOT_CUTOFF || pname == GL_CONSTANT_ATTENUATION || pname == GL_LINEAR_ATTENUATION || pname == GL_QUADRATIC_ATTENUATION), GL_INVALID_ENUM); if (type == GL_FLOAT) get_light_param(light, pname, static_cast(params)); else if (type == GL_INT) get_light_param(light, pname, static_cast(params)); else VERIFY_NOT_REACHED(); } void GLContext::gl_get_material(GLenum face, GLenum pname, void* params, GLenum type) { APPEND_TO_CALL_LIST_AND_RETURN_IF_NEEDED(gl_get_material, face, pname, params, type); RETURN_WITH_ERROR_IF(m_in_draw_state, GL_INVALID_OPERATION); RETURN_WITH_ERROR_IF(!(pname == GL_AMBIENT || pname == GL_DIFFUSE || pname == GL_SPECULAR || pname == GL_EMISSION), GL_INVALID_ENUM); RETURN_WITH_ERROR_IF(!(face == GL_FRONT || face == GL_BACK), GL_INVALID_ENUM); Face material_face = Front; switch (face) { case GL_FRONT: material_face = Front; break; case GL_BACK: material_face = Back; break; } if (type == GL_FLOAT) get_material_param(material_face, pname, static_cast(params)); else if (type == GL_INT) get_material_param(material_face, pname, static_cast(params)); else VERIFY_NOT_REACHED(); } void GLContext::gl_light_model(GLenum pname, GLfloat x, GLfloat y, GLfloat z, GLfloat w) { APPEND_TO_CALL_LIST_AND_RETURN_IF_NEEDED(gl_light_model, pname, x, y, z, w); RETURN_WITH_ERROR_IF(pname != GL_LIGHT_MODEL_AMBIENT && pname != GL_LIGHT_MODEL_COLOR_CONTROL && pname != GL_LIGHT_MODEL_LOCAL_VIEWER && pname != GL_LIGHT_MODEL_TWO_SIDE, GL_INVALID_ENUM); auto lighting_params = m_rasterizer->light_model(); switch (pname) { case GL_LIGHT_MODEL_AMBIENT: lighting_params.scene_ambient_color = { x, y, z, w }; break; case GL_LIGHT_MODEL_COLOR_CONTROL: { auto color_control = static_cast(x); RETURN_WITH_ERROR_IF(color_control != GL_SINGLE_COLOR && color_control != GL_SEPARATE_SPECULAR_COLOR, GL_INVALID_ENUM); lighting_params.color_control = (color_control == GL_SINGLE_COLOR) ? GPU::ColorControl::SingleColor : GPU::ColorControl::SeparateSpecularColor; break; } case GL_LIGHT_MODEL_LOCAL_VIEWER: // 0 means the viewer is at infinity // 1 means they're in local (eye) space lighting_params.viewer_at_infinity = (x == 0.f); break; case GL_LIGHT_MODEL_TWO_SIDE: lighting_params.two_sided_lighting = (x != 0.f); break; default: VERIFY_NOT_REACHED(); } m_rasterizer->set_light_model_params(lighting_params); } void GLContext::gl_light_modelv(GLenum pname, void const* params, GLenum type) { VERIFY(type == GL_FLOAT || type == GL_INT); auto parameters_to_vector = [&](T const* params) -> FloatVector4 { return (pname == GL_LIGHT_MODEL_AMBIENT) ? Vector4 { params[0], params[1], params[2], params[3] }.template to_type() : Vector4 { params[0], 0, 0, 0 }.template to_type(); }; auto light_model_parameters = (type == GL_FLOAT) ? parameters_to_vector(reinterpret_cast(params)) : parameters_to_vector(reinterpret_cast(params)); // Normalize integers to -1..1 if (pname == GL_LIGHT_MODEL_AMBIENT && type == GL_INT) light_model_parameters = (light_model_parameters + 2147483648.f) / 2147483647.5f - 1.f; gl_light_model(pname, light_model_parameters[0], light_model_parameters[1], light_model_parameters[2], light_model_parameters[3]); } void GLContext::gl_lightf(GLenum light, GLenum pname, GLfloat param) { APPEND_TO_CALL_LIST_AND_RETURN_IF_NEEDED(gl_lightf, light, pname, param); RETURN_WITH_ERROR_IF(m_in_draw_state, GL_INVALID_OPERATION); RETURN_WITH_ERROR_IF(light < GL_LIGHT0 || light >= (GL_LIGHT0 + m_device_info.num_lights), GL_INVALID_ENUM); RETURN_WITH_ERROR_IF(!(pname == GL_CONSTANT_ATTENUATION || pname == GL_LINEAR_ATTENUATION || pname == GL_QUADRATIC_ATTENUATION || pname != GL_SPOT_EXPONENT || pname != GL_SPOT_CUTOFF), GL_INVALID_ENUM); RETURN_WITH_ERROR_IF(param < 0.f, GL_INVALID_VALUE); auto& light_state = m_light_states.at(light - GL_LIGHT0); switch (pname) { case GL_CONSTANT_ATTENUATION: light_state.constant_attenuation = param; break; case GL_LINEAR_ATTENUATION: light_state.linear_attenuation = param; break; case GL_QUADRATIC_ATTENUATION: light_state.quadratic_attenuation = param; break; case GL_SPOT_EXPONENT: RETURN_WITH_ERROR_IF(param > 128.f, GL_INVALID_VALUE); light_state.spotlight_exponent = param; break; case GL_SPOT_CUTOFF: RETURN_WITH_ERROR_IF(param > 90.f && param != 180.f, GL_INVALID_VALUE); light_state.spotlight_cutoff_angle = param; break; default: VERIFY_NOT_REACHED(); } m_light_state_is_dirty = true; } void GLContext::gl_lightfv(GLenum light, GLenum pname, GLfloat const* params) { APPEND_TO_CALL_LIST_AND_RETURN_IF_NEEDED(gl_lightfv, light, pname, params); RETURN_WITH_ERROR_IF(m_in_draw_state, GL_INVALID_OPERATION); RETURN_WITH_ERROR_IF(light < GL_LIGHT0 || light >= (GL_LIGHT0 + m_device_info.num_lights), GL_INVALID_ENUM); RETURN_WITH_ERROR_IF(!(pname == GL_AMBIENT || pname == GL_DIFFUSE || pname == GL_SPECULAR || pname == GL_POSITION || pname == GL_CONSTANT_ATTENUATION || pname == GL_LINEAR_ATTENUATION || pname == GL_QUADRATIC_ATTENUATION || pname == GL_SPOT_CUTOFF || pname == GL_SPOT_EXPONENT || pname == GL_SPOT_DIRECTION), GL_INVALID_ENUM); auto& light_state = m_light_states.at(light - GL_LIGHT0); switch (pname) { case GL_AMBIENT: light_state.ambient_intensity = { params[0], params[1], params[2], params[3] }; break; case GL_DIFFUSE: light_state.diffuse_intensity = { params[0], params[1], params[2], params[3] }; break; case GL_SPECULAR: light_state.specular_intensity = { params[0], params[1], params[2], params[3] }; break; case GL_POSITION: light_state.position = { params[0], params[1], params[2], params[3] }; light_state.position = model_view_matrix() * light_state.position; break; case GL_CONSTANT_ATTENUATION: RETURN_WITH_ERROR_IF(params[0] < 0.f, GL_INVALID_VALUE); light_state.constant_attenuation = params[0]; break; case GL_LINEAR_ATTENUATION: RETURN_WITH_ERROR_IF(params[0] < 0.f, GL_INVALID_VALUE); light_state.linear_attenuation = params[0]; break; case GL_QUADRATIC_ATTENUATION: RETURN_WITH_ERROR_IF(params[0] < 0.f, GL_INVALID_VALUE); light_state.quadratic_attenuation = params[0]; break; case GL_SPOT_EXPONENT: { auto exponent = params[0]; RETURN_WITH_ERROR_IF(exponent < 0.f || exponent > 128.f, GL_INVALID_VALUE); light_state.spotlight_exponent = exponent; break; } case GL_SPOT_CUTOFF: { auto cutoff = params[0]; RETURN_WITH_ERROR_IF((cutoff < 0.f || cutoff > 90.f) && cutoff != 180.f, GL_INVALID_VALUE); light_state.spotlight_cutoff_angle = cutoff; break; } case GL_SPOT_DIRECTION: { FloatVector4 direction_vector = { params[0], params[1], params[2], 0.f }; direction_vector = model_view_matrix() * direction_vector; light_state.spotlight_direction = direction_vector.xyz(); break; } default: VERIFY_NOT_REACHED(); } m_light_state_is_dirty = true; } void GLContext::gl_lightiv(GLenum light, GLenum pname, GLint const* params) { APPEND_TO_CALL_LIST_AND_RETURN_IF_NEEDED(gl_lightiv, light, pname, params); RETURN_WITH_ERROR_IF(m_in_draw_state, GL_INVALID_OPERATION); RETURN_WITH_ERROR_IF(light < GL_LIGHT0 || light >= (GL_LIGHT0 + m_device_info.num_lights), GL_INVALID_ENUM); RETURN_WITH_ERROR_IF(!(pname == GL_AMBIENT || pname == GL_DIFFUSE || pname == GL_SPECULAR || pname == GL_POSITION || pname == GL_CONSTANT_ATTENUATION || pname == GL_LINEAR_ATTENUATION || pname == GL_QUADRATIC_ATTENUATION || pname == GL_SPOT_CUTOFF || pname == GL_SPOT_EXPONENT || pname == GL_SPOT_DIRECTION), GL_INVALID_ENUM); auto& light_state = m_light_states[light - GL_LIGHT0]; auto const to_float_vector = [](GLfloat x, GLfloat y, GLfloat z, GLfloat w) { return FloatVector4(x, y, z, w); }; switch (pname) { case GL_AMBIENT: light_state.ambient_intensity = to_float_vector(params[0], params[1], params[2], params[3]); break; case GL_DIFFUSE: light_state.diffuse_intensity = to_float_vector(params[0], params[1], params[2], params[3]); break; case GL_SPECULAR: light_state.specular_intensity = to_float_vector(params[0], params[1], params[2], params[3]); break; case GL_POSITION: light_state.position = to_float_vector(params[0], params[1], params[2], params[3]); light_state.position = model_view_matrix() * light_state.position; break; case GL_CONSTANT_ATTENUATION: RETURN_WITH_ERROR_IF(params[0] < 0, GL_INVALID_VALUE); light_state.constant_attenuation = static_cast(params[0]); break; case GL_LINEAR_ATTENUATION: RETURN_WITH_ERROR_IF(params[0] < 0, GL_INVALID_VALUE); light_state.linear_attenuation = static_cast(params[0]); break; case GL_QUADRATIC_ATTENUATION: RETURN_WITH_ERROR_IF(params[0] < 0, GL_INVALID_VALUE); light_state.quadratic_attenuation = static_cast(params[0]); break; case GL_SPOT_EXPONENT: { auto exponent = static_cast(params[0]); RETURN_WITH_ERROR_IF(exponent < 0.f || exponent > 128.f, GL_INVALID_VALUE); light_state.spotlight_exponent = exponent; break; } case GL_SPOT_CUTOFF: { auto cutoff = static_cast(params[0]); RETURN_WITH_ERROR_IF((cutoff < 0.f || cutoff > 90.f) && cutoff != 180.f, GL_INVALID_VALUE); light_state.spotlight_cutoff_angle = cutoff; break; } case GL_SPOT_DIRECTION: { auto direction_vector = to_float_vector(params[0], params[1], params[2], 0.0f); direction_vector = model_view_matrix() * direction_vector; light_state.spotlight_direction = direction_vector.xyz(); break; } default: VERIFY_NOT_REACHED(); } m_light_state_is_dirty = true; } void GLContext::gl_materialf(GLenum face, GLenum pname, GLfloat param) { APPEND_TO_CALL_LIST_AND_RETURN_IF_NEEDED(gl_materialf, face, pname, param); RETURN_WITH_ERROR_IF(!(face == GL_FRONT || face == GL_BACK || face == GL_FRONT_AND_BACK), GL_INVALID_ENUM); RETURN_WITH_ERROR_IF(pname != GL_SHININESS, GL_INVALID_ENUM); RETURN_WITH_ERROR_IF(param > 128.0f, GL_INVALID_VALUE); switch (face) { case GL_FRONT: m_material_states[Face::Front].shininess = param; break; case GL_BACK: m_material_states[Face::Back].shininess = param; break; case GL_FRONT_AND_BACK: m_material_states[Face::Front].shininess = param; m_material_states[Face::Back].shininess = param; break; default: VERIFY_NOT_REACHED(); } m_light_state_is_dirty = true; } void GLContext::gl_materialfv(GLenum face, GLenum pname, GLfloat const* params) { APPEND_TO_CALL_LIST_AND_RETURN_IF_NEEDED(gl_materialfv, face, pname, params); RETURN_WITH_ERROR_IF(!(face == GL_FRONT || face == GL_BACK || face == GL_FRONT_AND_BACK), GL_INVALID_ENUM); RETURN_WITH_ERROR_IF(!(pname == GL_AMBIENT || pname == GL_DIFFUSE || pname == GL_SPECULAR || pname == GL_EMISSION || pname == GL_SHININESS || pname == GL_AMBIENT_AND_DIFFUSE), GL_INVALID_ENUM); RETURN_WITH_ERROR_IF((pname == GL_SHININESS && *params > 128.0f), GL_INVALID_VALUE); auto update_material = [](GPU::Material& material, GLenum pname, GLfloat const* params) { switch (pname) { case GL_AMBIENT: material.ambient = { params[0], params[1], params[2], params[3] }; break; case GL_DIFFUSE: material.diffuse = { params[0], params[1], params[2], params[3] }; break; case GL_SPECULAR: material.specular = { params[0], params[1], params[2], params[3] }; break; case GL_EMISSION: material.emissive = { params[0], params[1], params[2], params[3] }; break; case GL_SHININESS: material.shininess = params[0]; break; case GL_AMBIENT_AND_DIFFUSE: material.ambient = { params[0], params[1], params[2], params[3] }; material.diffuse = { params[0], params[1], params[2], params[3] }; break; } }; switch (face) { case GL_FRONT: update_material(m_material_states[Face::Front], pname, params); break; case GL_BACK: update_material(m_material_states[Face::Back], pname, params); break; case GL_FRONT_AND_BACK: update_material(m_material_states[Face::Front], pname, params); update_material(m_material_states[Face::Back], pname, params); break; } m_light_state_is_dirty = true; } void GLContext::gl_materialiv(GLenum face, GLenum pname, GLint const* params) { APPEND_TO_CALL_LIST_AND_RETURN_IF_NEEDED(gl_materialiv, face, pname, params); RETURN_WITH_ERROR_IF(!(face == GL_FRONT || face == GL_BACK || face == GL_FRONT_AND_BACK), GL_INVALID_ENUM); RETURN_WITH_ERROR_IF(!(pname == GL_AMBIENT || pname == GL_DIFFUSE || pname == GL_SPECULAR || pname == GL_EMISSION || pname == GL_SHININESS || pname == GL_AMBIENT_AND_DIFFUSE), GL_INVALID_ENUM); RETURN_WITH_ERROR_IF((pname == GL_SHININESS && *params > 128), GL_INVALID_VALUE); auto update_material = [](GPU::Material& material, GLenum pname, GLint const* params) { switch (pname) { case GL_AMBIENT: material.ambient = { static_cast(params[0]), static_cast(params[1]), static_cast(params[2]), static_cast(params[3]) }; break; case GL_DIFFUSE: material.diffuse = { static_cast(params[0]), static_cast(params[1]), static_cast(params[2]), static_cast(params[3]) }; break; case GL_SPECULAR: material.specular = { static_cast(params[0]), static_cast(params[1]), static_cast(params[2]), static_cast(params[3]) }; break; case GL_EMISSION: material.emissive = { static_cast(params[0]), static_cast(params[1]), static_cast(params[2]), static_cast(params[3]) }; break; case GL_SHININESS: material.shininess = static_cast(params[0]); break; case GL_AMBIENT_AND_DIFFUSE: material.ambient = { static_cast(params[0]), static_cast(params[1]), static_cast(params[2]), static_cast(params[3]) }; material.diffuse = { static_cast(params[0]), static_cast(params[1]), static_cast(params[2]), static_cast(params[3]) }; break; } }; switch (face) { case GL_FRONT: update_material(m_material_states[Face::Front], pname, params); break; case GL_BACK: update_material(m_material_states[Face::Back], pname, params); break; case GL_FRONT_AND_BACK: update_material(m_material_states[Face::Front], pname, params); update_material(m_material_states[Face::Back], pname, params); break; } m_light_state_is_dirty = true; } void GLContext::gl_shade_model(GLenum mode) { APPEND_TO_CALL_LIST_AND_RETURN_IF_NEEDED(gl_shade_model, mode); RETURN_WITH_ERROR_IF(m_in_draw_state, GL_INVALID_OPERATION); RETURN_WITH_ERROR_IF(mode != GL_FLAT && mode != GL_SMOOTH, GL_INVALID_ENUM); auto options = m_rasterizer->options(); options.shade_smooth = (mode == GL_SMOOTH); m_rasterizer->set_options(options); } void GLContext::sync_light_state() { if (!m_light_state_is_dirty) return; m_light_state_is_dirty = false; auto options = m_rasterizer->options(); options.color_material_enabled = m_color_material_enabled; switch (m_color_material_face) { case GL_BACK: options.color_material_face = GPU::ColorMaterialFace::Back; break; case GL_FRONT: options.color_material_face = GPU::ColorMaterialFace::Front; break; case GL_FRONT_AND_BACK: options.color_material_face = GPU::ColorMaterialFace::FrontAndBack; break; default: VERIFY_NOT_REACHED(); } switch (m_color_material_mode) { case GL_AMBIENT: options.color_material_mode = GPU::ColorMaterialMode::Ambient; break; case GL_AMBIENT_AND_DIFFUSE: options.color_material_mode = GPU::ColorMaterialMode::AmbientAndDiffuse; break; case GL_DIFFUSE: options.color_material_mode = GPU::ColorMaterialMode::Diffuse; break; case GL_EMISSION: options.color_material_mode = GPU::ColorMaterialMode::Emissive; break; case GL_SPECULAR: options.color_material_mode = GPU::ColorMaterialMode::Specular; break; default: VERIFY_NOT_REACHED(); } m_rasterizer->set_options(options); for (auto light_id = 0u; light_id < m_device_info.num_lights; light_id++) { auto const& current_light_state = m_light_states.at(light_id); m_rasterizer->set_light_state(light_id, current_light_state); } m_rasterizer->set_material_state(GPU::Face::Front, m_material_states[Face::Front]); m_rasterizer->set_material_state(GPU::Face::Back, m_material_states[Face::Back]); } }