class TestWorldCoords(unittest.TestCase): def setUp(self): self.coords = WorldCoords([-1, -1, -1], 0.1) def test_fixed_to_world(self): f = [0, 1, 100] assert_array_max_ulp(self.coords.fixed_to_world(f), [-1.0, -0.9, 9.0], dtype=np.float32) def test_world_to_fixed(self): w = [-1.0, -0.9, 9.0] assert_array_equal(self.coords.world_to_fixed(w), [0, 1, 100]) def test_fixed_array_to_world(self): f = [[0, 1, 100], [20, 40, 60], [210, 310, 410]] assert_array_max_ulp( self.coords.fixed_to_world(f), [[-1.0, -0.9, 9.0], [1.0, 3.0, 5.0], [20.0, 30.0, 40.0]], dtype=np.float32) def test_world_array_to_fixed(self): w = [[-1.0, -0.9, 9.0], [1.0, 3.0, 5.0], [20.0, 30.0, 40.0]] assert_array_equal(self.coords.world_to_fixed(w), [[0, 1, 100], [20, 40, 60], [210, 310, 410]]) def test_out_of_range(self): with self.assertRaises(OutOfRangeError): self.coords.world_to_fixed([-2.0, 0.0, 0.0]) with self.assertRaises(OutOfRangeError): self.coords.world_to_fixed([0.0, 1e9, 0.0])
def create_bvh(): # 3 layer binary tree degree = 2 world_coords = WorldCoords(np.array([-1.0, -1.0, -1.0]), 0.1) layer_bounds = [0, 1, 3, 7] nodes = np.empty(shape=layer_bounds[-1], dtype=uint4) # bottom layer layer = lslice(layer_bounds, 2) nodes['x'][layer] = [0x00010000, 0x00020001, 0x00010000, 0x00010000] nodes['y'][layer] = [0x00010000, 0x00010000, 0x00020001, 0x00010000] nodes['z'][layer] = [0x00010000, 0x00010000, 0x00010000, 0x00020001] nodes['w'][layer] = 0x80000000 # leaf nodes # middle layer layer = lslice(layer_bounds, 1) nodes['x'][layer] = [0x00020000, 0x00010000] nodes['y'][layer] = [0x00010000, 0x00020000] nodes['z'][layer] = [0x00010000, 0x00020000] nodes['w'][layer] = [0x00000003, 0x00000005] # top layer layer = lslice(layer_bounds, 0) nodes['x'][layer] = [0x00020000] nodes['y'][layer] = [0x00020000] nodes['z'][layer] = [0x00020000] nodes['w'][layer] = [0x00000001] layer_offsets = list(layer_bounds[:-1]) # trim last entry bvh = BVH(world_coords=world_coords, nodes=nodes, layer_offsets=layer_offsets) return bvh
class TestWorldCoords(unittest.TestCase): def setUp(self): self.coords = WorldCoords([-1,-1,-1], 0.1) def test_fixed_to_world(self): f = [0, 1, 100] assert_array_max_ulp(self.coords.fixed_to_world(f), [-1.0, -0.9, 9.0], dtype=np.float32) def test_world_to_fixed(self): w = [-1.0, -0.9, 9.0] assert_array_equal(self.coords.world_to_fixed(w), [0, 1, 100]) def test_fixed_array_to_world(self): f = [[0, 1, 100], [20, 40, 60], [210, 310, 410]] assert_array_max_ulp(self.coords.fixed_to_world(f), [[-1.0, -0.9, 9.0], [1.0, 3.0, 5.0], [20.0, 30.0, 40.0]], dtype=np.float32) def test_world_array_to_fixed(self): w = [[-1.0, -0.9, 9.0], [1.0, 3.0, 5.0], [20.0, 30.0, 40.0]] assert_array_equal(self.coords.world_to_fixed(w), [[0, 1, 100], [20, 40, 60], [210, 310, 410]]) def test_out_of_range(self): with self.assertRaises(OutOfRangeError): self.coords.world_to_fixed([-2.0, 0.0, 0.0]) with self.assertRaises(OutOfRangeError): self.coords.world_to_fixed([0.0, 1e9, 0.0])
def create_leaf_nodes(mesh, morton_bits=16, round_to_multiple=1, nthreads_per_block=32, max_blocks=16): '''Compute the leaf nodes surrounding a triangle mesh. ``mesh``: chroma.geometry.Mesh Triangles to box ``morton_bits``: int Number of bits to use per dimension when computing Morton code. ``round_to_multiple``: int Round the number of nodes created up to multiple of this number Extra nodes will be all zero. Returns (world_coords, nodes, morton_codes), where ``world_coords``: chroma.bvh.WorldCoords Defines the fixed point coordinate system ``nodes``: ndarray(shape=len(mesh.triangles), dtype=uint4) List of leaf nodes. Child IDs will be set to triangle offsets. ``morton_codes``: ndarray(shape=len(mesh.triangles), dtype=np.uint64) Morton codes for each triangle, using ``morton_bits`` per axis. Must be <= 16 bits. ''' # it would be nice not to duplicate code, make functions transparent... context = None queue = None if gpuapi.is_gpu_api_opencl(): context = cltools.get_last_context() #print context queue = cl.CommandQueue(context) # Load GPU functions if gpuapi.is_gpu_api_cuda(): bvh_module = get_module('bvh.cu', options=api_options, include_source_directory=True) elif gpuapi.is_gpu_api_opencl(): # don't like the last context method. trouble. trouble. bvh_module = get_module('bvh.cl', cltools.get_last_context(), options=api_options, include_source_directory=True) bvh_funcs = GPUFuncs(bvh_module) # compute world coordinates world_origin_np = mesh.vertices.min(axis=0) world_scale = np.max( (mesh.vertices.max(axis=0) - world_origin_np)) / (2**16 - 2) world_coords = WorldCoords(world_origin=world_origin_np, world_scale=world_scale) # Put triangles and vertices into host and device memory # unfortunately, opencl and cuda has different methods for managing memory here # we have to write divergent code if gpuapi.is_gpu_api_cuda(): # here cuda supports a nice feature where we allocate host and device memory that are mapped onto one another. # no explicit requests for transfers here triangles = cutools.mapped_empty(shape=len(mesh.triangles), dtype=ga.vec.uint3, write_combined=True) triangles[:] = to_uint3(mesh.triangles) vertices = cutools.mapped_empty(shape=len(mesh.vertices), dtype=ga.vec.float3, write_combined=True) vertices[:] = to_float3(mesh.vertices) #print triangles[0:10] #print vertices[0:10] # Call GPU to compute nodes nodes = ga.zeros(shape=round_up_to_multiple(len(triangles), round_to_multiple), dtype=ga.vec.uint4) morton_codes = ga.empty(shape=len(triangles), dtype=np.uint64) # Convert world coords to GPU-friendly types world_origin = ga.vec.make_float3(*world_origin_np) world_scale = np.float32(world_scale) # generate morton codes on GPU for first_index, elements_this_iter, nblocks_this_iter in \ chunk_iterator(len(triangles), nthreads_per_block, max_blocks=30000): bvh_funcs.make_leaves(np.uint32(first_index), np.uint32(elements_this_iter), cutools.Mapped(triangles), cutools.Mapped(vertices), world_origin, world_scale, nodes, morton_codes, block=(nthreads_per_block, 1, 1), grid=(nblocks_this_iter, 1)) morton_codes_host = morton_codes.get() >> (16 - morton_bits) elif gpuapi.is_gpu_api_opencl(): # here we need to allocate a buffer on the host and on the device triangles = np.empty(len(mesh.triangles), dtype=ga.vec.uint3) copy_to_uint3(mesh.triangles, triangles) vertices = np.empty(len(mesh.vertices), dtype=ga.vec.float3) copy_to_float3(mesh.vertices, vertices) # now create a buffer object on the device and push data to it triangles_dev = ga.to_device(queue, triangles) vertices_dev = ga.to_device(queue, vertices) # Call GPU to compute nodes nodes = ga.zeros(queue, shape=round_up_to_multiple(len(triangles), round_to_multiple), dtype=ga.vec.uint4) morton_codes = ga.empty(queue, shape=len(triangles), dtype=np.uint64) # Convert world coords to GPU-friendly types #world_origin = np.array(world_origin_np,dtype=np.float32) world_origin = np.empty(1, dtype=ga.vec.float3) world_origin['x'] = world_origin_np[0] world_origin['y'] = world_origin_np[1] world_origin['z'] = world_origin_np[2] world_scale = np.float32(world_scale) #print world_origin, world_scale # generate morton codes on GPU for first_index, elements_this_iter, nblocks_this_iter in \ chunk_iterator(len(triangles), nthreads_per_block, max_blocks): print first_index, elements_this_iter, nblocks_this_iter bvh_funcs.make_leaves( queue, (nblocks_this_iter, 1, 1), (nthreads_per_block, 1, 1), #bvh_funcs.make_leaves( queue, (elements_this_iter,1,1), None, np.uint32(first_index), np.uint32(elements_this_iter), triangles_dev.data, vertices_dev.data, world_origin, world_scale, nodes.data, morton_codes.data, g_times_l=True).wait() morton_codes_host = morton_codes.get() >> (16 - morton_bits) return world_coords, nodes.get(), morton_codes_host
def setUp(self): self.coords = WorldCoords([-1, -1, -1], 0.1)
def setUp(self): self.coords = WorldCoords([-1,-1,-1], 0.1)
def create_leaf_nodes(mesh, morton_bits=16, round_to_multiple=1): '''Compute the leaf nodes surrounding a triangle mesh. ``mesh``: chroma.geometry.Mesh Triangles to box ``morton_bits``: int Number of bits to use per dimension when computing Morton code. ``round_to_multiple``: int Round the number of nodes created up to multiple of this number Extra nodes will be all zero. Returns (world_coords, nodes, morton_codes), where ``world_coords``: chroma.bvh.WorldCoords Defines the fixed point coordinate system ``nodes``: ndarray(shape=len(mesh.triangles), dtype=uint4) List of leaf nodes. Child IDs will be set to triangle offsets. ``morton_codes``: ndarray(shape=len(mesh.triangles), dtype=np.uint64) Morton codes for each triangle, using ``morton_bits`` per axis. Must be <= 16 bits. ''' # Load GPU functions bvh_module = get_cu_module('bvh.cu', options=cuda_options, include_source_directory=True) bvh_funcs = GPUFuncs(bvh_module) # compute world coordinates world_origin = mesh.vertices.min(axis=0) world_scale = np.max((mesh.vertices.max(axis=0) - world_origin)) \ / (2**16 - 2) world_coords = WorldCoords(world_origin=world_origin, world_scale=world_scale) # Put triangles and vertices in mapped host memory triangles = mapped_empty(shape=len(mesh.triangles), dtype=ga.vec.uint3, write_combined=True) triangles[:] = to_uint3(mesh.triangles) vertices = mapped_empty(shape=len(mesh.vertices), dtype=ga.vec.float3, write_combined=True) vertices[:] = to_float3(mesh.vertices) # Call GPU to compute nodes nodes = ga.zeros(shape=round_up_to_multiple(len(triangles), round_to_multiple), dtype=ga.vec.uint4) morton_codes = ga.empty(shape=len(triangles), dtype=np.uint64) # Convert world coords to GPU-friendly types world_origin = ga.vec.make_float3(*world_origin) world_scale = np.float32(world_scale) nthreads_per_block = 256 for first_index, elements_this_iter, nblocks_this_iter in \ chunk_iterator(len(triangles), nthreads_per_block, max_blocks=30000): bvh_funcs.make_leaves(np.uint32(first_index), np.uint32(elements_this_iter), Mapped(triangles), Mapped(vertices), world_origin, world_scale, nodes, morton_codes, block=(nthreads_per_block, 1, 1), grid=(nblocks_this_iter, 1)) morton_codes_host = morton_codes.get() >> (16 - morton_bits) return world_coords, nodes.get(), morton_codes_host