"""Tests for backend render-state helpers."""
import unittest
from unittest.mock import call, patch
from OpenGL.GL import (
GL_BLEND,
GL_CULL_FACE,
GL_DEPTH_TEST,
GL_FLOAT,
GL_LIGHT0,
GL_LIGHTING,
GL_LINE,
GL_MODELVIEW,
GL_ONE,
GL_POSITION,
GL_TEXTURE_2D,
GL_UNSIGNED_INT,
GL_ZERO,
)
from OpenGL.raw.GL.VERSION.GL_1_0 import (
GL_AMBIENT,
GL_DIFFUSE,
GL_FRONT_AND_BACK,
GL_PROJECTION,
GL_SHININESS,
GL_SPECULAR,
)
from picogl.backend.gl.backend import GLBackend
from picogl.backend.gl.capability import (
GLBlendFactor,
GLFixedFunctionCapability,
GLMaterialFace,
GLPipelineCapability,
PhongMaterial,
)
from picogl.backend.gl.driver.blend import GLBlendDriver
from picogl.backend.gl.driver.capability import GLCapabilityDriver
from picogl.backend.gl.driver.depth import GLDepthDriver
from picogl.backend.gl.driver.frame import GLFrameDriver
from picogl.backend.gl.driver.geometry import GLGeometryDriver
from picogl.backend.gl.driver.raster import GLRasterDriver, resolve_polygon_mode_args
from picogl.backend.gl.driver.texture import GLTextureSystem
from picogl.backend.gl.state.client import GLClientState
from picogl.backend.gl.state.fill import GLFace, GLFillMode, GLLightParameter
from picogl.backend.legacy.core.attribute_binder import LegacyAttributeBinder
from picogl.backend.legacy.core.pipeline import GLLegacyPipeline
from picogl.backend.state import (
BlendState,
DepthState,
DrawCommand,
GLClipPlaneState,
RasterState,
RenderState,
RenderStateApplier,
gl_value,
)
from picogl.core.polygon.mode import PolygonMode
[docs]
class _RecordingRaster:
def __init__(self, calls):
[docs]
def set_line_width(self, width):
self.calls.append(("line_width", width))
[docs]
def set_polygon_mode(self, face, mode):
self.calls.append(("polygon_mode", face, mode))
[docs]
def apply(self, state):
self.set_line_width(state.line_width)
self.set_polygon_mode(GLFace.FRONT_AND_BACK, gl_value(state.polygon_mode))
if state.polygon_offset != (0.0, 0.0):
self.calls.append(("polygon_offset", state.polygon_offset))
if state.point_size is not None:
self.calls.append(("point_size", state.point_size))
[docs]
class _RecordingDepth:
def __init__(self, calls):
[docs]
def set_depth_test(self, enabled):
self.calls.append(("depth_test", enabled))
[docs]
def set_depth_write(self, enabled):
self.calls.append(("depth_write", enabled))
[docs]
def apply(self, state):
self.set_depth_test(state.test)
self.set_depth_write(state.write)
[docs]
class _RecordingBlend:
def __init__(self, calls):
[docs]
def set_blend(self, enabled):
self.calls.append(("blend", enabled))
[docs]
def set_blend_func(self, src, dst):
self.calls.append(("blend_func", src, dst))
[docs]
def apply(self, state):
self.set_blend(state.enabled)
if state.enabled:
self.set_blend_func(gl_value(state.src), gl_value(state.dst))
[docs]
class _RecordingCapabilities:
def __init__(self, calls):
[docs]
def set_enabled(self, cap, enabled):
self.calls.append(("enabled", cap, enabled))
[docs]
class RecordingBackend:
def __init__(self):
[docs]
self.blend = _RecordingBlend(self.calls)
[docs]
self.depth = _RecordingDepth(self.calls)
[docs]
self.raster = _RecordingRaster(self.calls)
[docs]
self.capabilities = _RecordingCapabilities(self.calls)
[docs]
def enable(self, cap):
self.calls.append(("enable", cap))
[docs]
def disable(self, cap):
self.calls.append(("disable", cap))
[docs]
def set_line_width(self, width):
self.calls.append(("line_width", width))
[docs]
def set_polygon_mode(self, *args):
self.calls.append(("polygon_mode", args))
[docs]
def set_depth_write(self, enabled):
self.calls.append(("depth_write", enabled))
[docs]
def apply_state(self, state):
self.calls.append(("apply_state", state))
[docs]
def bind_texture(self, texture_id):
self.calls.append(("bind_texture", texture_id))
[docs]
def draw_mesh(self, mesh, mode):
self.calls.append(("draw_mesh", mesh, mode))
[docs]
class FakeBinding:
[docs]
def bind_mesh(self, mesh):
pass
[docs]
def draw(self, mesh, mode):
pass
[docs]
class RecordingBinding:
def __init__(self):
[docs]
def bind_mesh(self, mesh):
self.calls.append(("bind_mesh", mesh))
[docs]
def draw(self, mesh, mode):
self.calls.append(("draw", mesh, mode))
[docs]
class FakeTextureDriver:
def __init__(self):
[docs]
def create(self, tex):
self.calls.append(("create", tex))
tex.handle = 42
[docs]
def bind(self, tex):
self.calls.append(("bind", tex))
[docs]
def initialize(self, tex):
self.calls.append(("initialize", tex))
[docs]
class TestRenderState(unittest.TestCase):
[docs]
def test_flat_constructor_matches_elmo_context_usage(self):
state = RenderState(
blend=True,
depth_write=False,
lighting=True,
line_width=2.5,
polygon_mode=GL_LINE,
)
self.assertTrue(state.blend)
self.assertFalse(state.depth_write)
self.assertTrue(state.depth_test)
self.assertTrue(state.lighting)
self.assertEqual(state.line_width, 2.5)
self.assertEqual(state.polygon_mode, GL_LINE)
self.assertEqual(
state.raster, RasterState(polygon_mode=GL_LINE, line_width=2.5)
)
self.assertEqual(state.depth, DepthState(test=True, write=False))
[docs]
def test_default_constructor_stores_semantic_values(self):
state = RenderState()
self.assertEqual(state.blend_src, GLBlendFactor.SRC_ALPHA)
self.assertEqual(state.blend_dst, GLBlendFactor.ONE_MINUS_SRC_ALPHA)
self.assertEqual(state.polygon_mode, GLFillMode.FILL)
[docs]
class TestRenderStateApplier(unittest.TestCase):
[docs]
def test_applies_state_once_and_skips_identical_state(self):
backend = RecordingBackend()
applier = RenderStateApplier(backend)
state = RenderState(
blend=True,
blend_src=GLBlendFactor.ONE,
blend_dst=GLBlendFactor.ZERO,
depth_write=False,
line_width=2.0,
polygon_mode=GLFillMode.LINE,
lighting=True,
)
applier.apply(state)
first_call_count = len(backend.calls)
applier.apply(state)
self.assertEqual(len(backend.calls), first_call_count)
self.assertIn(("blend", True), backend.calls)
self.assertIn(("blend_func", GL_ONE, GL_ZERO), backend.calls)
self.assertIn(("polygon_mode", GLFace.FRONT_AND_BACK, GL_LINE), backend.calls)
self.assertIn(("depth_write", False), backend.calls)
self.assertIn(
("enabled", GLFixedFunctionCapability.LIGHTING, True),
backend.calls,
)
applier.apply(RenderState(lighting=False))
self.assertNotIn(
("enabled", GLFixedFunctionCapability.LIGHTING, False),
backend.calls,
)
[docs]
def test_depth_state_applies_depth_not_blend(self):
backend = RecordingBackend()
depth = DepthState(test=False, write=False)
depth.apply(
state=type(
"State",
(),
{
"backend": backend,
"set_enabled": lambda _self, cap, enabled: backend.calls.append(
("enabled", cap, enabled)
),
},
)()
)
call_names = [call[0] for call in backend.calls]
self.assertIn("depth_test", call_names)
self.assertIn("depth_write", call_names)
self.assertNotIn("blend", call_names)
[docs]
class TestDrawCommand(unittest.TestCase):
[docs]
def test_draw_command_uses_backend_state_texture_and_draw_mesh(self):
backend = RecordingBackend()
mesh = object()
state = RenderState(blend=True)
command = DrawCommand(mesh=mesh, mode=GL_LINE, texture=7, state=state)
command.execute(backend)
self.assertEqual(
backend.calls,
[
("apply_state", state),
("bind_texture", 7),
("draw_mesh", mesh, GL_LINE),
],
)
[docs]
def test_glbackend_exposes_state_api(self):
backend = GLBackend(binding=FakeBinding())
self.assertTrue(hasattr(backend, "apply_state"))
self.assertTrue(hasattr(backend, "draw_command"))
self.assertIsInstance(backend.frame, GLFrameDriver)
self.assertIsInstance(backend.raster, GLRasterDriver)
self.assertIsInstance(backend.legacy, GLLegacyPipeline)
[docs]
def test_raster_driver_delegates_to_opengl(self):
raster = GLRasterDriver()
with (
patch("picogl.backend.gl.driver.raster.glLineWidth") as line_width,
patch("picogl.backend.gl.driver.raster.glPolygonMode") as polygon_mode,
patch("picogl.backend.gl.driver.raster.glPointSize") as point_size,
patch(
"picogl.backend.gl.driver.raster.glGetFloatv",
return_value=(1.0, 10.0),
) as get_float,
patch("picogl.backend.gl.driver.raster.glPolygonOffset") as polygon_offset,
):
raster.set_line_width(2.0)
raster.set_polygon_mode(GLFace.FRONT_AND_BACK, GL_LINE)
raster.set_point_size(3.0)
raster.set_clamped_point_size(12.0)
raster.set_polygon_offset(-1.0, -1.0)
line_width.assert_called_once_with(2.0)
polygon_mode.assert_called_once_with(GLFace.FRONT_AND_BACK, GL_LINE)
self.assertEqual(point_size.call_args_list, [call(3.0), call(10.0)])
get_float.assert_called_once()
polygon_offset.assert_called_once_with(-1.0, -1.0)
[docs]
def test_raster_driver_caches_line_width(self):
raster = GLRasterDriver()
with patch("picogl.backend.gl.driver.raster.glLineWidth") as line_width:
raster.set_line_width(2.0)
raster.set_line_width(2.0)
line_width.assert_called_once_with(2.0)
[docs]
def test_raster_driver_get_line_width_uses_cache(self):
raster = GLRasterDriver()
with (
patch("picogl.backend.gl.driver.raster.glLineWidth"),
patch("picogl.backend.gl.driver.raster.glGetFloatv") as get_float,
):
raster.set_line_width(2.5)
self.assertEqual(raster.get_line_width(), 2.5)
get_float.assert_not_called()
[docs]
def test_raster_driver_caches_polygon_mode(self):
raster = GLRasterDriver()
with patch("picogl.backend.gl.driver.raster.glPolygonMode") as polygon_mode:
raster.set_polygon_mode(GLFace.FRONT_AND_BACK, GL_LINE)
raster.set_polygon_mode(GLFace.FRONT_AND_BACK, GL_LINE)
polygon_mode.assert_called_once_with(GL_FRONT_AND_BACK, GL_LINE)
[docs]
def test_raster_shared_api_uses_singleton_cache(self):
GLRasterDriver._shared = None
shared = GLRasterDriver.shared()
with patch("picogl.backend.gl.driver.raster.glLineWidth"):
GLRasterDriver.shared().set_line_width(4.0)
self.assertEqual(shared.get_line_width(), 4.0)
[docs]
def test_glbackend_owns_per_instance_raster_driver(self):
GLRasterDriver._shared = None
backend = GLBackend(binding=FakeBinding())
with patch("picogl.backend.gl.driver.raster.glLineWidth"):
backend.raster.set_line_width(4.0)
self.assertIsNot(backend.raster, GLRasterDriver.shared())
self.assertEqual(backend.raster.get_line_width(), 4.0)
[docs]
def test_raster_isolated_instance_does_not_use_shared(self):
GLRasterDriver._shared = None
shared = GLRasterDriver.shared()
shared.set_line_width(1.0)
isolated = GLRasterDriver()
with patch("picogl.backend.gl.driver.raster.glLineWidth"):
isolated.set_line_width(3.0)
self.assertEqual(isolated.get_line_width(), 3.0)
self.assertEqual(shared.get_line_width(), 1.0)
[docs]
def test_resolve_polygon_mode_args(self):
self.assertEqual(
resolve_polygon_mode_args(GL_LINE),
(GLFace.FRONT_AND_BACK, GL_LINE),
)
self.assertEqual(
resolve_polygon_mode_args(GLFace.FRONT, GL_LINE),
(GLFace.FRONT, GL_LINE),
)
with self.assertRaises(TypeError):
resolve_polygon_mode_args()
[docs]
def test_raster_driver_apply_raster_state(self):
raster = GLRasterDriver()
state = RasterState(line_width=2.5, polygon_mode=GLFillMode.LINE)
with (
patch("picogl.backend.gl.driver.raster.glLineWidth") as line_width,
patch("picogl.backend.gl.driver.raster.glPolygonMode") as polygon_mode,
):
raster.apply(state)
raster.apply(state)
line_width.assert_called_once_with(2.5)
polygon_mode.assert_called_once_with(GL_FRONT_AND_BACK, GL_LINE)
[docs]
def test_state_subsystem_drivers_delegate_to_opengl(self):
capabilities = GLCapabilityDriver()
depth = GLDepthDriver(capabilities)
blend = GLBlendDriver(capabilities)
with (
patch("picogl.backend.gl.driver.capability.gl_enable") as enable,
patch("picogl.backend.gl.driver.capability.glDisable") as disable,
patch(
"picogl.backend.gl.driver.capability.glIsEnabled", return_value=True
) as is_enabled,
patch("picogl.backend.gl.driver.depth.glDepthMask") as depth_mask,
patch("picogl.backend.gl.driver.depth.glDepthFunc") as depth_func,
patch("picogl.backend.gl.driver.blend.glBlendFunc") as blend_func,
):
capabilities.enable(GL_CULL_FACE)
capabilities.disable(GL_LIGHTING)
self.assertTrue(capabilities.is_enabled(GL_DEPTH_TEST))
depth.set_depth_test(True)
depth.set_depth_write(False)
depth.set_depth_func_gl_less()
blend.set_blend(True)
blend.set_blend_func(GL_ONE, GL_ZERO)
self.assertEqual(
enable.call_args_list,
[call(GL_CULL_FACE), call(GL_DEPTH_TEST), call(GL_BLEND)],
)
disable.assert_called_once_with(GL_LIGHTING)
is_enabled.assert_called_once_with(GL_DEPTH_TEST)
depth_mask.assert_called_once_with(False)
depth_func.assert_called_once()
blend_func.assert_called_once_with(GL_ONE, GL_ZERO)
[docs]
def test_glbackend_state_facade_uses_state_subsystems(self):
backend = GLBackend(binding=FakeBinding())
with (
patch.object(backend.capabilities, "enable") as enable,
patch.object(backend.capabilities, "disable") as disable,
patch.object(
backend.capabilities, "is_enabled", return_value=True
) as is_enabled,
patch.object(backend.capabilities, "set_enabled") as set_enabled,
patch.object(backend.depth, "set_depth_test") as set_depth_test,
patch.object(backend.depth, "set_depth_write") as set_depth_write,
patch.object(backend.depth, "set_depth_func_gl_less") as set_depth_func,
patch.object(backend.blend, "set_blend") as set_blend,
patch.object(backend.blend, "set_blend_func") as set_blend_func,
patch.object(backend.blend, "setup_blending") as setup_blending,
):
backend.capabilities.enable(GL_CULL_FACE)
backend.capabilities.disable(GL_LIGHTING)
self.assertTrue(backend.capabilities.is_enabled(GL_DEPTH_TEST))
backend.depth.set_depth_test(True)
backend.depth.set_depth_write(False)
backend.depth.set_depth_func_gl_less()
backend.blend.set_blend(True)
backend.blend.set_blend_func(GL_ONE, GL_ZERO)
backend.blend.setup_blending()
backend.capabilities.set_enabled(GLPipelineCapability.CULL_FACE, True)
backend.capabilities.set_enabled(GLFixedFunctionCapability.LIGHTING, False)
enable.assert_called_once_with(GL_CULL_FACE)
disable.assert_called_once_with(GL_LIGHTING)
is_enabled.assert_called_once_with(GL_DEPTH_TEST)
set_depth_test.assert_called_once_with(True)
set_depth_write.assert_called_once_with(False)
set_depth_func.assert_called_once_with()
set_blend.assert_called_once_with(True)
set_blend_func.assert_called_once_with(GL_ONE, GL_ZERO)
setup_blending.assert_called_once_with()
self.assertEqual(
set_enabled.call_args_list,
[
call(GLPipelineCapability.CULL_FACE, True),
call(GLFixedFunctionCapability.LIGHTING, False),
],
)
[docs]
def test_glbackend_polygon_mode_uses_raster_driver(self):
backend = GLBackend(binding=FakeBinding())
with (
patch.object(backend.raster, "set_line_width") as set_line_width,
patch.object(backend.raster, "set_polygon_mode") as set_polygon_mode,
patch.object(backend.raster, "set_point_size") as set_point_size,
patch.object(
backend.raster,
"set_clamped_point_size",
) as set_clamped_point_size,
patch.object(backend.raster, "set_polygon_offset") as set_polygon_offset,
):
backend.raster.set_line_width(2.0)
backend.raster.set_polygon_mode(GLFace.FRONT_AND_BACK, GL_LINE)
backend.raster.set_point_size(3.0)
backend.raster.set_clamped_point_size(4.0)
backend.raster.set_polygon_offset(-1.0, -1.0)
set_line_width.assert_called_once_with(2.0)
set_polygon_mode.assert_called_once_with(GLFace.FRONT_AND_BACK, GL_LINE)
set_point_size.assert_called_once_with(3.0)
set_clamped_point_size.assert_called_once_with(4.0)
set_polygon_offset.assert_called_once_with(-1.0, -1.0)
[docs]
def test_legacy_pipeline_delegates_to_opengl(self):
legacy = GLLegacyPipeline()
material = PhongMaterial(
ambient=(0.1, 0.2, 0.3, 1.0),
diffuse=(0.4, 0.5, 0.6, 1.0),
specular=(0.7, 0.8, 0.9, 1.0),
shininess=32.0,
)
with (
patch("picogl.backend.legacy.core.pipeline.glMatrixMode") as matrix_mode,
patch(
"picogl.backend.legacy.core.pipeline.glLoadIdentity"
) as load_identity,
patch("picogl.backend.legacy.core.pipeline.gluPerspective") as perspective,
patch("picogl.backend.legacy.core.pipeline.glTranslatef") as translate,
patch("picogl.backend.legacy.core.pipeline.glLightfv") as lightfv,
patch("picogl.backend.legacy.core.pipeline.glMaterialfv") as materialfv,
patch("picogl.backend.legacy.core.pipeline.glMaterialf") as materialf,
):
legacy.set_projection(45.0, 1.5, 0.1, 1000.0)
legacy.translate(1, 2, 3)
legacy.set_light([0.0, 0.0, 10.0, 1.0])
legacy.set_material(GLMaterialFace.FRONT_AND_BACK, material)
self.assertEqual(
matrix_mode.call_args_list, [call(GL_PROJECTION), call(GL_MODELVIEW)]
)
load_identity.assert_called_once_with()
perspective.assert_called_once_with(45.0, 1.5, 0.1, 1000.0)
translate.assert_called_once_with(1.0, 2.0, 3.0)
lightfv.assert_called_once_with(GL_LIGHT0, GL_POSITION, [0.0, 0.0, 10.0, 1.0])
self.assertEqual(
materialfv.call_args_list,
[
call(GLFace.FRONT_AND_BACK, GLLightParameter.AMBIENT, material.ambient),
call(GLFace.FRONT_AND_BACK, GLLightParameter.DIFFUSE, material.diffuse),
call(
GLFace.FRONT_AND_BACK, GLLightParameter.SPECULAR, material.specular
),
],
)
materialf.assert_called_once_with(
GLFace.FRONT_AND_BACK,
GL_SHININESS,
material.shininess,
)
[docs]
def test_glbackend_fixed_function_uses_legacy_pipeline(self):
backend = GLBackend(binding=FakeBinding())
material = PhongMaterial(
ambient=(0.1, 0.2, 0.3, 1.0),
diffuse=(0.4, 0.5, 0.6, 1.0),
specular=(0.7, 0.8, 0.9, 1.0),
shininess=32.0,
)
with (
patch.object(backend.legacy, "set_projection") as set_projection,
patch.object(backend.legacy, "translate") as translate,
patch.object(backend.legacy, "set_light") as set_light,
patch.object(backend.legacy, "set_material") as set_material,
):
backend.legacy.set_projection(45.0, 1.5, 0.1, 1000.0)
backend.legacy.translate(1, 2, 3)
backend.legacy.set_light([0.0, 0.0, 10.0, 1.0])
backend.legacy.set_material(GLMaterialFace.FRONT_AND_BACK, material)
set_projection.assert_called_once_with(45.0, 1.5, 0.1, 1000.0)
translate.assert_called_once_with(1, 2, 3)
set_light.assert_called_once_with(
[0.0, 0.0, 10.0, 1.0],
)
set_material.assert_called_once_with(GLMaterialFace.FRONT_AND_BACK, material)
[docs]
def test_geometry_driver_delegates_to_binding_and_draw_elements(self):
binding = RecordingBinding()
geometry = GLGeometryDriver(binding)
mesh = object()
with (
patch(
"picogl.backend.gl.driver.geometry.gl_draw_elements"
) as draw_elements,
patch("picogl.backend.gl.driver.geometry.gl_draw_arrays") as draw_arrays,
patch("picogl.backend.gl.driver.geometry.glBindVertexArray") as bind_vao,
):
geometry.draw_mesh(mesh, GL_LINE)
geometry.draw_elements(GL_LINE, [0, 1, 2])
geometry.draw_bound_elements(GL_LINE, 4, GL_UNSIGNED_INT, None)
geometry.draw_arrays(GL_LINE, 2, 5)
geometry.draw_arrays_bound_vao(9, GL_LINE, 1, 6)
self.assertEqual(binding.calls, [("bind_mesh", mesh), ("draw", mesh, GL_LINE)])
self.assertEqual(
draw_elements.call_args_list,
[
call(GL_LINE, 3, GL_UNSIGNED_INT, [0, 1, 2]),
call(GL_LINE, 4, GL_UNSIGNED_INT, None),
],
)
self.assertEqual(
draw_arrays.call_args_list,
[call(GL_LINE, 2, 5), call(GL_LINE, 1, 6)],
)
self.assertEqual(bind_vao.call_args_list, [call(9), call(0)])
[docs]
def test_texture_system_owns_texture_lifecycle(self):
driver = FakeTextureDriver()
textures = GLTextureSystem(driver=driver)
with (
patch("picogl.backend.gl.driver.texture.gl_bind_texture") as bind_texture,
patch(
"picogl.backend.gl.driver.texture.glDeleteTextures"
) as delete_textures,
):
handle = textures.create_texture(4, 5, data=None)
textures.bind_texture(7)
textures.delete_texture(7)
self.assertEqual(handle, 42)
self.assertEqual(
[call[0] for call in driver.calls], ["create", "bind", "initialize"]
)
self.assertEqual(driver.calls[0][1].spec.width, 4)
self.assertEqual(driver.calls[0][1].spec.height, 5)
bind_texture.assert_called_once_with(GL_TEXTURE_2D, 7)
delete_textures.assert_called_once_with([7])
[docs]
def test_legacy_attribute_binder_delegates_to_client_state_calls(self):
binder = LegacyAttributeBinder()
data = object()
with (
patch(
"picogl.wrappers.client_state.gl_enable_legacy_client_state"
) as enable_client,
patch("picogl.wrappers.pointer.gl_vertex_array_pointer") as vertex_pointer,
patch("picogl.wrappers.pointer.gl_normal_array_pointer") as normal_pointer,
patch("picogl.wrappers.pointer.gl_color_array_pointer") as color_pointer,
patch(
"picogl.wrappers.pointer.gl_texcoord_array_pointer"
) as texcoord_pointer,
):
binder.enable_vertex_array()
binder.set_vertex_pointer(data)
binder.enable_normal_array()
binder.set_normal_pointer(data)
binder.enable_color_array()
binder.set_color_pointer(data, 4)
binder.enable_texcoord_array()
binder.set_texcoord_pointer(data)
self.assertEqual(
enable_client.call_args_list,
[
call(GLClientState.VERTEX),
call(GLClientState.NORMAL),
call(GLClientState.COLOR),
call(GLClientState.TEXCOORD),
],
)
vertex_pointer.assert_called_once_with(
pointer=data, size=3, num_type=GL_FLOAT, stride=0
)
normal_pointer.assert_called_once_with(
pointer=data, num_type=GL_FLOAT, stride=0
)
color_pointer.assert_called_once_with(
pointer=data, size=4, num_type=GL_FLOAT, stride=0
)
texcoord_pointer.assert_called_once_with(
pointer=data, size=2, num_type=GL_FLOAT, stride=0
)
[docs]
def test_glbackend_geometry_texture_facades_and_attribute_subsystem(self):
backend = GLBackend(binding=FakeBinding())
mesh = object()
data = object()
with (
patch.object(backend.geometry, "draw_mesh") as draw_mesh,
patch.object(backend.geometry, "draw_elements") as draw_elements,
patch.object(
backend.geometry, "draw_bound_elements"
) as draw_bound_elements,
patch.object(backend.geometry, "draw_arrays") as draw_arrays,
patch.object(
backend.geometry,
"draw_arrays_bound_vao",
) as draw_arrays_bound_vao,
patch.object(
backend.textures, "create_texture", return_value=9
) as create_texture,
patch.object(backend.textures, "bind_texture") as bind_texture,
patch.object(backend.textures, "delete_texture") as delete_texture,
patch.object(
backend.attributes, "enable_vertex_array"
) as enable_vertex_array,
patch.object(
backend.attributes, "set_vertex_pointer"
) as set_vertex_pointer,
patch.object(
backend.attributes, "enable_normal_array"
) as enable_normal_array,
patch.object(
backend.attributes, "set_normal_pointer"
) as set_normal_pointer,
patch.object(
backend.attributes, "enable_color_array"
) as enable_color_array,
patch.object(backend.attributes, "set_color_pointer") as set_color_pointer,
patch.object(
backend.attributes, "enable_texcoord_array"
) as enable_texcoord_array,
patch.object(
backend.attributes, "set_texcoord_pointer"
) as set_texcoord_pointer,
):
backend.geometry.draw_mesh(mesh, GL_LINE)
backend.geometry.draw_elements(GL_LINE, [0, 1, 2])
backend.geometry.draw_bound_elements(GL_LINE, 4, GL_UNSIGNED_INT, None)
backend.geometry.draw_arrays(GL_LINE, 1, 6)
backend.geometry.draw_arrays_bound_vao(9, GL_LINE, 2, 7)
self.assertEqual(backend.textures.create_texture(4, 5, None), 9)
backend.textures.bind_texture(7)
backend.textures.delete_texture(7)
backend.attributes.enable_vertex_array()
backend.attributes.set_vertex_pointer(data)
backend.attributes.enable_normal_array()
backend.attributes.set_normal_pointer(data)
backend.attributes.enable_color_array()
backend.attributes.set_color_pointer(data, 4)
backend.attributes.enable_texcoord_array()
backend.attributes.set_texcoord_pointer(data)
draw_mesh.assert_called_once_with(mesh, GL_LINE)
draw_elements.assert_called_once_with(GL_LINE, [0, 1, 2])
draw_bound_elements.assert_called_once_with(GL_LINE, 4, GL_UNSIGNED_INT, None)
draw_arrays.assert_called_once_with(GL_LINE, 1, 6)
draw_arrays_bound_vao.assert_called_once_with(9, GL_LINE, 2, 7)
create_texture.assert_called_once_with(4, 5, None)
bind_texture.assert_called_once_with(7)
delete_texture.assert_called_once_with(7)
enable_vertex_array.assert_called_once_with()
set_vertex_pointer.assert_called_once_with(data)
enable_normal_array.assert_called_once_with()
set_normal_pointer.assert_called_once_with(data)
enable_color_array.assert_called_once_with()
set_color_pointer.assert_called_once_with(data, 4)
enable_texcoord_array.assert_called_once_with()
set_texcoord_pointer.assert_called_once_with(data)
[docs]
def test_glbackend_fixed_function_delegates_to_opengl(self):
backend = GLBackend(binding=FakeBinding())
with (
patch("picogl.backend.gl.driver.frame.glViewport") as viewport,
patch(
"picogl.backend.legacy.core.pipeline.glLoadIdentity"
) as load_identity,
patch("picogl.backend.legacy.core.pipeline.glTranslatef") as translate,
patch("picogl.backend.legacy.core.pipeline.glLightfv") as lightfv,
patch("picogl.backend.gl.driver.frame.glClearColor") as clear_color,
):
backend.frame.viewport(1, 2, 3, 4)
backend.legacy.load_identity()
backend.legacy.translate(1, 2, 3)
backend.legacy.set_light([0.0, 0.0, 10.0, 1.0])
backend.frame.set_clear_color((0.1, 0.2, 0.3, 1.0))
viewport.assert_called_once_with(1, 2, 3, 4)
load_identity.assert_called_once_with()
translate.assert_called_once_with(1.0, 2.0, 3.0)
lightfv.assert_called_once_with(GL_LIGHT0, GL_POSITION, [0.0, 0.0, 10.0, 1.0])
clear_color.assert_called_once_with(0.1, 0.2, 0.3, 1.0)
[docs]
def test_glbackend_applies_clip_state(self):
backend = GLBackend(binding=FakeBinding())
clip = GLClipPlaneState(enabled0=True, enabled1=False)
with patch.object(clip, "apply") as apply:
backend.clip.apply(clip)
self.assertIs(backend.clip, clip)
apply.assert_called_once_with(backend.state_manager)
[docs]
def test_glbackend_applies_stored_clip_state(self):
backend = GLBackend(binding=FakeBinding())
with patch.object(backend.clip, "apply") as apply:
backend.apply_clip_state()
apply.assert_called_once_with(backend.state_manager)
[docs]
def test_glbackend_perspective_projection_delegates_to_opengl(self):
backend = GLBackend(binding=FakeBinding())
with (
patch("picogl.backend.legacy.core.pipeline.glMatrixMode") as matrix_mode,
patch(
"picogl.backend.legacy.core.pipeline.glLoadIdentity"
) as load_identity,
patch("picogl.backend.legacy.core.pipeline.gluPerspective") as perspective,
):
backend.legacy.set_projection(45.0, 1.5, 0.1, 1000.0)
self.assertEqual(
matrix_mode.call_args_list,
[
call(GL_PROJECTION),
call(GL_MODELVIEW),
],
)
load_identity.assert_called_once_with()
perspective.assert_called_once_with(45.0, 1.5, 0.1, 1000.0)
[docs]
def test_glbackend_set_material_delegates_to_opengl(self):
backend = GLBackend(binding=FakeBinding())
material = PhongMaterial(
ambient=(0.1, 0.2, 0.3, 1.0),
diffuse=(0.4, 0.5, 0.6, 1.0),
specular=(0.7, 0.8, 0.9, 1.0),
shininess=32.0,
)
with (
patch("picogl.backend.legacy.core.pipeline.glMaterialfv") as materialfv,
patch("picogl.backend.legacy.core.pipeline.glMaterialf") as materialf,
):
backend.legacy.set_material(GLMaterialFace.FRONT_AND_BACK, material)
self.assertEqual(
materialfv.call_args_list,
[
call(GLFace.FRONT_AND_BACK, GL_AMBIENT, material.ambient),
call(GLFace.FRONT_AND_BACK, GL_DIFFUSE, material.diffuse),
call(GLFace.FRONT_AND_BACK, GL_SPECULAR, material.specular),
],
)
materialf.assert_called_once_with(
GLFace.FRONT_AND_BACK,
GL_SHININESS,
material.shininess,
)
if __name__ == "__main__":
unittest.main()