make impossible state unrepresentable

Lesley Lai

http://lesleylai.info/

Motivation

struct QueueFamilyIndices {
    std::optional<uint32_t> graphics;
    std::optional<uint32_t> present;
    std::optional<uint32_t> compute;

    bool isComplete() const {
        return graphics.has_value()
        && present.has_value()
        && compute.has_value();
    }
};

Motivation

QueueFamilyIndices findQueueFamilies(/*...*/) {
  // ...
  QueueFamilyIndices indices;
  for (const auto& queue: queues) {
    if (/* queue i support graphics */) {
        indices.graphics = i;
    }

    if (/* queue i support present */) {
        indices.present = i;
    }
    
    if (/* queue i support compute */) {
        indices.compute = i;
    }

    if (indices.isComplete()) {
        break;
    }
  }
  return indices;
}

Transformation

struct QueueFamilyIndices {
    uint32_t graphics;
    uint32_t present;
    uint32_t compute;
};

Transformation

std::optional<QueueFamilyIndices> findQueueFamilies(/*...*/) {
  // ...
  std::optional<uint32_t> graphicsFamily = std::nullopt;
  std::optional<uint32_t> presentFamily = std::nullopt;
  std::optional<uint32_t> computeFamily = std::nullopt;

  for (const auto& queue: queues) {
    if (/* queue i support graphics */) {
        graphicsFamily = i;
    }

    if (/* queue i support present */) {
        presentFamily = i;
    }
    
    if (/* queue i support compute */) {
        computeFamily = i;
    }

    if (graphicsFamily && presentFamily && computeFamily) {
        return QueueFamilyIndices{*graphicsFamily, *presentFamily,
                                  *computeFamily};
    }
  }
  
  return std::nullopt;
}

Memory footprint gets reduced

Less assertion or run-time checking

API becomes cleaner

Title Text

struct DrawCommand {
  std::uint32_t count;
  std::uint32_t vertex_offset;
  std::uint32_t instance_count;
};

struct DrawIndirectCommand {
  void* indirect;
};

struct BindGraphicsPipelineCommand {
  GraphicsPipelineHandle pipeline;
};

using Command = std::variant<DrawCommand, DrawIndirectCommand
                             BindGraphicsPipelineCommand, ...>;

struct CommandBuffer {
  void push_command(Command command);
  std::vector<Command> commands;
};

An example with variant

Title Text

struct DrawCommand {
  std::uint32_t count;
  std::uint32_t vertex_offset;
  std::uint32_t instance_count;
  
  GraphicsPipelineHandle pipeline;
};

struct DrawIndirectCommand {
  void* indirect;
  
  GraphicsPipelineHandle pipeline;
};

using Command = std::variant<DrawCommand, DrawIndirectCommand>;

class CommandBuffer {
  void push_command(Command command);
  std::vector<Command> commands;
};

An example with variant

Title Text

struct DrawCommand {
  std::uint32_t count;
  std::uint32_t vertex_offset;
  std::uint32_t instance_count;
};

struct DrawIndirectCommand {
  void* indirect;
};

using Command = std::variant<DrawCommand, DrawIndirectCommand>;

class SecondaryCommandBuffer {
  void push_command(Command command);
  std::vector<Command> commands;
  GraphicsPipelineHandle pipeline;
};

class CommandBuffer {
  void push_commands(SecondaryCommandBuffer buffer);
  std::vector<Command> secondary_buffers;
};

An example with variant

The pitfall of Move semantics

The pitfall of Move semantics

class Window {
  // ...
  
  Window(Window&& other) noexcept = delete;
  Window& operator=(Window&& other) noexcept = delete;

private:
  std::reference_wrapper<GLFWwindow> window;
}

The pitfall of Move semantics

class Window {
  // ...
  
  Window(Window&& other) noexcept : window{other.window} {
    other.window = nullptr;
  }

private:
  GLFWwindow* window;
}

Thank you