示例#1
0
def create_debugger():
    global debugger

    # Debug report setup
    debugger = Debugger(api, instance)
    debugger.start()
示例#2
0
class Engine(object):

    def __init__(self, configuration=None):
        self.configuration = configuration or DEFAULT_ENGINE_CONFIGURATION
        self.window = w = Window(width=800, height=600)

        self.running = False
        self.debug = False

        self.api = self.instance = self.device = self.physical_device = None
        self.debugger = self.debug_ui = self.surface = self.render_queue = None
        self.queues = self.swapchain = self.swapchain_images = None
        self.command_pool = self.setup_command_buffer = self.setup_fence = None
        self.info = {}

        self.graph = []
        self.current_scene_index = None

        self._setup_instance()
        self._setup_debugger()
        self._setup_surface()
        self._setup_device()
        self._setup_device_info()
        self._setup_swapchain()
        self._setup_setup_commands()

        self.memory_manager = MemoryManager(self)
        self.render_target = RenderTarget(self)

        self.renderer = Renderer(self)
        self.compute_runner = ComputeRunner(self)

    def free(self):
        api, i, d = self.api, self.instance, self.device

        hvk.device_wait_idle(api, d)

        hvk.destroy_command_pool(api, d, self.command_pool)
        hvk.destroy_fence(api, d, self.setup_fence)

        for scene in self.graph:
            scene.free()

        self.compute_runner.free()
        self.renderer.free()
        self.render_target.free()
        self.memory_manager.free()

        hvk.destroy_swapchain(api, d, self.swapchain)
        hvk.destroy_device(api, d)

        hvk.destroy_surface(api, i, self.surface)

        if self.debugger is not None:
            self.debugger.stop()

        if self.debug_ui is not None:
            self.debug_ui.close()

        hvk.destroy_instance(api, i)

        self.window.destroy()

    def load(self, scene):
        if scene.loaded:
            return

        scene_data = DataScene(self, scene)
        scene.id = len(self.graph)
        self.graph.append(scene_data)

        scene.on_initialized()

    def activate(self, scene):
        assert scene.id is not None, "Scene was not loaded in engine"

        # Skip any setup events in the system event queue
        w = self.window
        w.show()
        w.translate_system_events()
        w.events.clear()

        self.running = True
        self.current_scene_index = scene.id

        if self.debug_ui is not None:
            scene_data = self.graph[scene.id]
            self.debug_ui.load_scene(scene_data)

    def update(self):
        scene_data = self.graph[self.current_scene_index]
        scene_data.apply_updates()

    def events(self):
        scene_data = self.graph[self.current_scene_index]
        scene = scene_data.scene
        e, w = evt, self.window

        w.translate_system_events()

        for event, data in w.events:
            if event is e.MouseMove:
                scene.on_mouse_move(event, data)
            elif event is e.MouseClick:
                scene.on_mouse_click(event, data)
            elif event is e.MouseScroll:
                scene.on_mouse_scroll(event, data)
            elif event is e.KeyPress:
                scene.on_key_pressed(event, data)
            elif event is e.WindowResized:
                self._update_swapchain(data)
                scene.on_window_resized(event, data)
            elif event is e.RenderDisable:
                self.renderer.disable()
            elif event is e.RenderEnable:
                self.renderer.enable()

        if self.debug_ui is not None:
            self.debug_ui.events()

        if w.must_exit:
            self.running = False

    def render(self):
        scene_data = self.graph[self.current_scene_index]
        self.renderer.render(scene_data)

    def compute(self, scene, compute, group, sync=False, before=None, after=None, callback=None):
        assert scene.id is not None, "Scene was not loaded in engine"
        assert compute.id is not None, "Compute was not loaded in engine"

        data_scene = self.graph[scene.id]
        data_compute = data_scene.computes[compute.id]
        self.compute_runner.run(data_scene, data_compute, group, sync=sync, before=before, after=after, callback=callback)

    def submit_setup_command(self, wait=False):
        api, device = self.api, self.device
        fence = 0

        if wait:
            fence = self.setup_fence

        infos = (hvk.submit_info(command_buffers=(self.setup_command_buffer,)),)
        hvk.queue_submit(api, self.render_queue.handle, infos, fence)

        if wait:
            f = (fence,)
            hvk.wait_for_fences(api, device, f)
            hvk.reset_fences(api, device, f)

    # Setup functions

    def _setup_instance(self):
        layers = []
        extensions = ["VK_KHR_surface", hvk.SYSTEM_SURFACE_EXTENSION]

        available_layers = [l[0] for l in hvk.enumerate_layers()]

        if DEBUG and "VK_LAYER_LUNARG_standard_validation" in available_layers:
            self.debug = True
            extensions.append("VK_EXT_debug_utils")
            layers.append("VK_LAYER_LUNARG_standard_validation")

        self.api, self.instance = hvk.create_instance(extensions, layers)

    def _setup_debugger(self):
        from vulkan.debugger import Debugger

        if not self.debug:
            self.debugger = None
            return

        self.debugger = Debugger(self.api, self.instance)
        self.debugger.start()    

        self.debug_ui = DebugUI(self)

    def _setup_surface(self):
        self.surface = hvk.create_surface(self.api, self.instance, self.window)
    
    def _setup_device(self):
        api, instance = self.api, self.instance
        conf = self.configuration
        queues_conf = conf.get("QUEUES", DEFAULT_ENGINE_CONFIGURATION["QUEUES"])

        # Device selection
        failure_reasons, all_good = [], False
        physical_devices = hvk.list_physical_devices(api, instance)
        
        for index, physical_device in enumerate(physical_devices):
            
            # Features
            supported_features = hvk.physical_device_features(api, physical_device)
            if not "texture_compression_BC" in supported_features:
                failure_reasons.append(f"BC compressed textures are not supported on your machine on device #{index}")
                continue

            # Queues
            queue_families = hvk.list_queue_families(api, physical_device)
            queue_create_infos = []
            mapped_queue_configurations = {}
            render_queue_data = None
            bad_queues = False

            for qconf in queues_conf:
                queue_family = None

                # Find a matching queue family
                for qf in queue_families:
                    if qconf.type in IntFlag(qf.properties.queue_flags):
                        queue_family = qf
                
                # Go to the next device if a required queue configuration could not be found
                if qconf.required and queue_family is None:
                    bad_queues = True
                    failure_reasons.append(f"No queue family matches the required configuration {qconf} on device #{index}")
                    break

                # Update the queue create info array
                new_info = {"queue_family_index": queue_family.index, "queue_count": 0}
                queue_create_info = next((qc for qc in queue_create_infos if qc["queue_family_index"] == queue_family.index), new_info)
                queue_create_infos.append(queue_create_info)
                queue_local_index = queue_create_info["queue_count"]
                queue_create_info["queue_count"] += 1
            
                # Save the index of the required graphics queue that will be used by the renderer
                if render_queue_data is None and QueueType.Graphics in qconf.type:
                    render_queue_data = (queue_family, queue_local_index)

                # Associate the queue configuration with the family and the local index for queue handle fetching
                mapped_queue_configurations[qconf] = (queue_family, queue_local_index)

            if bad_queues:
                continue

            all_good = True
            break

        if not all_good:
            raise RuntimeError(failure_reasons)

        if render_queue_data is None:
            raise RuntimeError("No graphics queue was specified in the engine queues configuration. Protip: use \"QueueConf.Default\" ")

        # Device creation
        queue_create_infos = [hvk.queue_create_info(**args) for args in queue_create_infos]
        extensions = ("VK_KHR_swapchain",)
        features = vk.PhysicalDeviceFeatures(texture_compression_BC = 1)
        device = hvk.create_device(api, physical_device, extensions, queue_create_infos, features)

        # Fetching queues
        # First, fetch the render queue
        render_queue_family, render_family_local_index = render_queue_data
        render_queue_handle = hvk.get_queue(api, device, render_queue_family.index, render_family_local_index)
        render_queue = Queue(render_queue_handle, render_queue_family)

        # Second, fetch all the queues (yes, the render queue is fetched twice)
        queues = {}
        for queue_conf, queue_data in mapped_queue_configurations.items():
            queue_family, local_index = queue_data
            queue_handle = hvk.get_queue(api, device, queue_family.index, local_index)
            queues[queue_conf.name] = Queue(queue_handle, queue_family)

        self.physical_device = physical_device
        self.device = device
        self.render_queue = render_queue
        self.queues = queues

    def _setup_device_info(self):
        api, physical_device = self.api, self.physical_device
        info = self.info

        properties = hvk.physical_device_properties(api, physical_device)
        info["limits"] = properties.limits

        depth_formats = (vk.FORMAT_D32_SFLOAT_S8_UINT, vk.FORMAT_D24_UNORM_S8_UINT, vk.FORMAT_D16_UNORM_S8_UINT)
        for fmt in depth_formats:
            prop = hvk.physical_device_format_properties(api, physical_device, fmt)
            if vk.FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT & prop.optimal_tiling_features != 0:
                info["depth_format"] = fmt
                break

        if "depth_format" not in info:
            raise RuntimeError("Failed to find a suitable depth stencil format.")

    def _setup_swapchain(self):
        api, device, physical_device, surface = self.api, self.device, self.physical_device, self.surface
        render_queue = self.render_queue
        old_swapchain = self.swapchain

        # Swapchain Setup
        caps = hvk.physical_device_surface_capabilities(api, physical_device, surface)
        formats = hvk.physical_device_surface_formats(api, physical_device, surface)
        present_modes = hvk.physical_device_surface_present_modes(api, physical_device, surface)

        if not hvk.get_physical_device_surface_support(api, physical_device, surface, render_queue.family.index):
            raise RuntimeError("Main rendering queue cannot present images to the surface")

        # Swapchain Format
        format_values = tuple(vkf.format for vkf in formats)
        required_formats = [vk.FORMAT_B8G8R8A8_UNORM, vk.FORMAT_B8G8R8A8_SRGB]
        for i, required_format in enumerate(required_formats):
            if required_format in format_values:
                required_formats[i] = format_values.index(required_format)
            else:
                required_formats[i] = None

        selected_format = next((formats[i] for i in required_formats if i is not None), None)
        if selected_format is None:
            raise RuntimeError("Required swapchain image format not supported")

        # Swapchain Extent
        extent = caps.current_extent
        if extent.width == -1 or extent.height == -1:
            width, height = self.window.dimensions()
            extent.width = width
            extent.height = height

        # Min image count
        min_image_count = 2
        if caps.max_image_count != 0 and caps.max_image_count < min_image_count:
            raise RuntimeError("Minimum image count not met")
        elif caps.min_image_count > min_image_count:
            min_image_count = caps.min_image_count

        # Present mode
        present_mode = vk.PRESENT_MODE_FIFO_KHR
        if vk.PRESENT_MODE_MAILBOX_KHR in present_modes:
            present_mode = vk.PRESENT_MODE_MAILBOX_KHR
        elif vk.PRESENT_MODE_IMMEDIATE_KHR in present_modes:
            present_mode = vk.PRESENT_MODE_IMMEDIATE_KHR

        # Default image transformation
        transform = caps.current_transform
        if vk.SURFACE_TRANSFORM_IDENTITY_BIT_KHR in IntFlag(caps.supported_transforms):
            transform = vk.SURFACE_TRANSFORM_IDENTITY_BIT_KHR

        # Swapchain creation
        old_swapchain = old_swapchain or 0
        swapchain_image_format = selected_format.format
        self.swapchain = hvk.create_swapchain(api, device, hvk.swapchain_create_info(
            surface = surface,
            image_format = swapchain_image_format,
            image_color_space = selected_format.color_space,
            image_extent = extent,
            min_image_count = min_image_count,
            present_mode = present_mode,
            pre_transform = transform,
            image_usage = vk.IMAGE_USAGE_COLOR_ATTACHMENT_BIT | vk.IMAGE_USAGE_TRANSFER_DST_BIT,
            old_swapchain = old_swapchain
        ))

        hvk.destroy_swapchain(api, device, old_swapchain)

        self.info["swapchain_extent"] = OrderedDict(width=extent.width, height=extent.height)
        self.info["swapchain_format"] = swapchain_image_format

    def _setup_setup_commands(self):
        api, device = self.api, self.device
        render_queue = self.render_queue

        command_pool = hvk.create_command_pool(api, device, hvk.command_pool_create_info(
            queue_family_index = render_queue.family.index,
            flags = vk.COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT
        ))

        cmds = hvk.allocate_command_buffers(api, device, hvk.command_buffer_allocate_info(
            command_pool = command_pool,
            command_buffer_count = 1
        ))

        fence_info = hvk.fence_create_info()
        fence = hvk.create_fence(api, device, fence_info)


        self.command_pool = command_pool
        self.setup_command_buffer = cmds[0]
        self.setup_fence = fence

    # Update functions

    def _update_swapchain(self, resize_data):
        hvk.device_wait_idle(self.api, self.device)
        self._setup_swapchain()
        self.render_target._update_swapchain()
        self.renderer._setup_render_cache()
        self.graph[self.current_scene_index]._setup_render_cache()