test_cg_shader.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. import os
  2. import platform
  3. import pytest
  4. from _pytest.outcomes import Failed
  5. from panda3d import core
  6. SHADERS_DIR = core.Filename.from_os_specific(os.path.dirname(__file__))
  7. # This is the template for the shader that is used by run_cg_test.
  8. # We render this to an nx1 texture, where n is the number of lines in the body.
  9. # An assert
  10. CG_VERTEX_TEMPLATE = """//Cg
  11. void vshader(float4 vtx_position : POSITION, out float4 l_position : POSITION) {{
  12. l_position = vtx_position;
  13. }}
  14. """
  15. CG_FRAGMENT_TEMPLATE = """//Cg
  16. {preamble}
  17. float4 _assert(bool cond) {{
  18. return float4(cond.x, 1, 1, 1);
  19. }}
  20. float4 _assert(bool2 cond) {{
  21. return float4(cond.x, cond.y, 1, 1);
  22. }}
  23. float4 _assert(bool3 cond) {{
  24. return float4(cond.x, cond.y, cond.z, 1);
  25. }}
  26. float4 _assert(bool4 cond) {{
  27. return float4(cond.x, cond.y, cond.z, cond.w);
  28. }}
  29. #define assert(cond) {{ if ((int)l_vpos.x == __LINE__ - line_offset) o_color = _assert(cond); }}
  30. void fshader(in float4 l_vpos : VPOS, out float4 o_color : COLOR) {{
  31. o_color = float4(1, 1, 1, 1);
  32. if ((int)l_vpos.x == 0) {{
  33. o_color = float4(0, 0, 0, 0);
  34. }}
  35. const int line_offset = __LINE__;
  36. {body}
  37. }}
  38. """
  39. def run_cg_test(gsg, body, preamble="", inputs={},
  40. state=core.RenderState.make_empty()):
  41. """ Runs a Cg test on the given GSG. The given body is executed in the
  42. main function and should call assert(). The preamble should contain all
  43. of the shader inputs. """
  44. if not gsg.supports_basic_shaders:
  45. pytest.skip("basic shaders not supported")
  46. __tracebackhide__ = True
  47. preamble = preamble.strip()
  48. body = body.rstrip().lstrip('\n')
  49. num_lines = body.count('\n') + 1
  50. vertex_code = CG_VERTEX_TEMPLATE.format(preamble=preamble, body=body)
  51. code = CG_FRAGMENT_TEMPLATE.format(preamble=preamble, body=body)
  52. shader = core.Shader.make(core.Shader.SL_Cg, vertex_code, code)
  53. if not shader:
  54. pytest.fail("error compiling shader:\n" + code)
  55. result = core.Texture("")
  56. fbprops = core.FrameBufferProperties()
  57. fbprops.force_hardware = True
  58. fbprops.set_rgba_bits(8, 8, 8, 8)
  59. fbprops.srgb_color = False
  60. engine = gsg.get_engine()
  61. buffer = engine.make_output(
  62. gsg.pipe,
  63. 'buffer',
  64. 0,
  65. fbprops,
  66. core.WindowProperties.size(core.Texture.up_to_power_2(num_lines + 1), 1),
  67. core.GraphicsPipe.BF_refuse_window,
  68. gsg
  69. )
  70. buffer.add_render_texture(result, core.GraphicsOutput.RTM_copy_ram, core.GraphicsOutput.RTP_color)
  71. buffer.set_clear_color_active(True)
  72. buffer.set_clear_color((0, 0, 0, 0))
  73. engine.open_windows()
  74. # Build up the shader inputs
  75. attrib = core.ShaderAttrib.make(shader)
  76. for name, value in inputs.items():
  77. attrib = attrib.set_shader_input(name, value)
  78. state = state.set_attrib(attrib)
  79. scene = core.NodePath("root")
  80. scene.set_attrib(core.DepthTestAttrib.make(core.RenderAttrib.M_always))
  81. format = core.GeomVertexFormat.get_v3()
  82. vdata = core.GeomVertexData("tri", format, core.Geom.UH_static)
  83. vdata.unclean_set_num_rows(3)
  84. vertex = core.GeomVertexWriter(vdata, "vertex")
  85. vertex.set_data3(-1, -1, 0)
  86. vertex.set_data3(3, -1, 0)
  87. vertex.set_data3(-1, 3, 0)
  88. tris = core.GeomTriangles(core.Geom.UH_static)
  89. tris.add_next_vertices(3)
  90. geom = core.Geom(vdata)
  91. geom.add_primitive(tris)
  92. gnode = core.GeomNode("tri")
  93. gnode.add_geom(geom, state)
  94. scene.attach_new_node(gnode)
  95. scene.set_two_sided(True)
  96. camera = scene.attach_new_node(core.Camera("camera"))
  97. camera.node().get_lens(0).set_near_far(-10, 10)
  98. camera.node().set_cull_bounds(core.OmniBoundingVolume())
  99. region = buffer.make_display_region()
  100. region.active = True
  101. region.camera = camera
  102. try:
  103. engine.render_frame()
  104. except AssertionError as exc:
  105. assert False, "Error executing shader:\n" + code
  106. finally:
  107. engine.remove_window(buffer)
  108. # Download the texture to check whether the assertion triggered.
  109. triggered = tuple(result.get_ram_image())
  110. if triggered[0]:
  111. pytest.fail("control check failed")
  112. if not all(triggered[4:]):
  113. count = 0
  114. lines = body.split('\n')
  115. formatted = ''
  116. for i, line in enumerate(lines):
  117. offset = (i + 1) * 4
  118. x = triggered[offset + 2] == 0
  119. y = triggered[offset + 1] == 0
  120. z = triggered[offset] == 0
  121. w = triggered[offset + 3] == 0
  122. if x or y or z or w:
  123. count += 1
  124. else:
  125. continue
  126. formatted += '=> ' + line
  127. components = ''
  128. if x:
  129. components += 'x'
  130. if y:
  131. components += 'y'
  132. if z:
  133. components += 'z'
  134. if w:
  135. components += 'w'
  136. formatted += f' <= {components} components don\'t match'
  137. formatted += '\n'
  138. pytest.fail("{0} Cg assertions triggered:\n{1}".format(count, formatted))
  139. def run_cg_compile_check(gsg, shader_path, expect_fail=False):
  140. """Compile supplied Cg shader path and check for errors"""
  141. shader = core.Shader.load(shader_path, core.Shader.SL_Cg)
  142. # assert shader.is_prepared(gsg.prepared_objects)
  143. if expect_fail:
  144. assert shader is None
  145. else:
  146. assert shader is not None
  147. def test_cg_compile_error(gsg):
  148. """Test getting compile errors from bad Cg shaders"""
  149. shader_path = core.Filename(SHADERS_DIR, 'cg_bad.sha')
  150. run_cg_compile_check(gsg, shader_path, expect_fail=True)
  151. def test_cg_from_file(gsg):
  152. """Test compiling Cg shaders from files"""
  153. shader_path = core.Filename(SHADERS_DIR, 'cg_simple.sha')
  154. run_cg_compile_check(gsg, shader_path)
  155. def test_cg_test(gsg):
  156. "Test to make sure that the Cg tests work correctly."
  157. run_cg_test(gsg, "assert(true);")
  158. def test_cg_test_fail(gsg):
  159. "Same as above, but making sure that the failure case works correctly."
  160. with pytest.raises(Failed):
  161. run_cg_test(gsg, "assert(false);")
  162. def test_cg_sampler(gsg):
  163. tex1 = core.Texture("tex1-ubyte-rgba8")
  164. tex1.setup_1d_texture(1, core.Texture.T_unsigned_byte, core.Texture.F_rgba8)
  165. tex1.set_clear_color((0, 2 / 255.0, 1, 1))
  166. tex2 = core.Texture("tex2-float-rgba32")
  167. tex2.setup_2d_texture(1, 1, core.Texture.T_float, core.Texture.F_rgba32)
  168. tex2.set_clear_color((1.0, 2.0, -3.14, 0.0))
  169. tex3 = core.Texture("tex3-float-r32")
  170. tex3.setup_3d_texture(1, 1, 1, core.Texture.T_float, core.Texture.F_r32)
  171. tex3.set_clear_color((0.5, 0.0, 0.0, 1.0))
  172. preamble = """
  173. uniform sampler1D tex1;
  174. uniform sampler2D tex2;
  175. uniform sampler3D tex3;
  176. """
  177. code = """
  178. assert(tex1D(tex1, 0) == float4(0, 2 / 255.0, 1, 1));
  179. assert(tex2D(tex2, float2(0, 0)) == float4(1.0, 2.0, -3.14, 0.0));
  180. assert(abs(tex3D(tex3, float3(0, 0, 0)).r - 0.5) < 0.01);
  181. """
  182. run_cg_test(gsg, code, preamble, {'tex1': tex1, 'tex2': tex2, 'tex3': tex3})
  183. def test_cg_int(gsg):
  184. inputs = dict(
  185. zero=0,
  186. ten=10,
  187. intmax=0x7fffffff,
  188. intmin=-0x7fffffff,
  189. )
  190. preamble = """
  191. uniform int zero;
  192. uniform int intmax;
  193. uniform int intmin;
  194. """
  195. code = """
  196. assert(zero == 0);
  197. assert(intmax == 0x7fffffff);
  198. assert(intmin == -0x7fffffff);
  199. """
  200. run_cg_test(gsg, code, preamble, inputs)
  201. def test_cg_state_material(gsg):
  202. mat = core.Material("mat")
  203. mat.ambient = (1, 2, 3, 4)
  204. mat.diffuse = (5, 6, 7, 8)
  205. mat.emission = (9, 10, 11, 12)
  206. mat.specular = (13, 14, 15, 0)
  207. mat.shininess = 16
  208. preamble = """
  209. uniform float4x4 attr_material;
  210. """
  211. code = """
  212. assert(attr_material[0] == float4(1, 2, 3, 4));
  213. assert(attr_material[1] == float4(5, 6, 7, 8));
  214. assert(attr_material[2] == float4(9, 10, 11, 12));
  215. assert(attr_material[3].rgb == float3(13, 14, 15));
  216. assert(attr_material[3].w == 16);
  217. """
  218. node = core.NodePath("state")
  219. node.set_material(mat)
  220. run_cg_test(gsg, code, preamble, state=node.get_state())
  221. def test_cg_state_fog(gsg):
  222. fog = core.Fog("fog")
  223. fog.color = (1, 2, 3, 4)
  224. fog.exp_density = 0.5
  225. fog.set_linear_range(6, 10)
  226. preamble = """
  227. uniform float4 attr_fog;
  228. uniform float4 attr_fogcolor;
  229. """
  230. code = """
  231. assert(attr_fogcolor == float4(1, 2, 3, 4));
  232. assert(attr_fog[0] == 0.5);
  233. assert(attr_fog[1] == 6);
  234. assert(attr_fog[2] == 10);
  235. assert(attr_fog[3] == 0.25);
  236. """
  237. node = core.NodePath("state")
  238. node.set_fog(fog)
  239. run_cg_test(gsg, code, preamble, state=node.get_state())
  240. def test_cg_texpad_texpix(gsg):
  241. tex = core.Texture("test")
  242. tex.setup_2d_texture(16, 32, core.Texture.T_unsigned_byte, core.Texture.F_rgba)
  243. tex.auto_texture_scale = core.ATS_pad
  244. tex.set_size_padded(10, 30)
  245. preamble = """
  246. uniform float3 texpad_test;
  247. uniform float2 texpix_test;
  248. """
  249. code = """
  250. assert(texpad_test == float3(10 * 0.5 / 16, 30 * 0.5 / 32, 0.5));
  251. assert(texpix_test == float2(1.0 / 16, 1.0 / 32));
  252. """
  253. run_cg_test(gsg, code, preamble, inputs={"test": tex})
  254. def test_cg_alight(gsg):
  255. alight = core.AmbientLight("alight")
  256. alight.set_color((1, 2, 3, 4))
  257. np = core.NodePath(alight)
  258. preamble = """
  259. uniform float4 alight_test;
  260. """
  261. code = """
  262. assert(alight_test == float4(1, 2, 3, 4));
  263. """
  264. run_cg_test(gsg, code, preamble, inputs={"test": np})
  265. def test_cg_satten(gsg):
  266. spot = core.Spotlight("spot")
  267. spot.set_attenuation((1, 2, 3))
  268. spot.set_exponent(4)
  269. np = core.NodePath(spot)
  270. preamble = """
  271. uniform float4 satten_test;
  272. """
  273. code = """
  274. assert(satten_test == float4(1, 2, 3, 4));
  275. """
  276. run_cg_test(gsg, code, preamble, inputs={"test": np})