mirror of
https://github.com/ZDoom/gzdoom.git
synced 2025-01-22 01:11:49 +00:00
Import ZDRay's GPURaytracer class as VkLightmap as the first step in letting the backend update them dynamically
This commit is contained in:
parent
a45fab65e8
commit
c5b03e016e
10 changed files with 5669 additions and 0 deletions
672
libraries/dp_rect_pack/dp_rect_pack.h
Normal file
672
libraries/dp_rect_pack/dp_rect_pack.h
Normal file
|
@ -0,0 +1,672 @@
|
|||
/*
|
||||
* Rectangle packing library.
|
||||
* v1.1.3
|
||||
*
|
||||
* Copyright (c) 2017-2021 Daniel Plakhotich
|
||||
*
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgement in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DP_RECT_PACK_H
|
||||
#define DP_RECT_PACK_H
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
|
||||
#define DP_RECT_PACK_VERSION_MAJOR 1
|
||||
#define DP_RECT_PACK_VERSION_MINOR 1
|
||||
#define DP_RECT_PACK_VERSION_PATCH 3
|
||||
|
||||
|
||||
namespace dp {
|
||||
namespace rect_pack {
|
||||
|
||||
|
||||
/**
|
||||
* Status of the RectPacker::InsertResult.
|
||||
*
|
||||
* Only InsertStatus::ok indicates a successful insertion;
|
||||
* all other values are kinds of errors.
|
||||
*/
|
||||
struct InsertStatus {
|
||||
enum Type {
|
||||
ok, ///< Successful insertion
|
||||
negativeSize, ///< Width and/or height is negative
|
||||
zeroSize, ///< Width and/or height is zero
|
||||
|
||||
/**
|
||||
* Rectangle is too big to fit in a single page.
|
||||
*
|
||||
* Width and/or height of the rectangle exceeds the maximum
|
||||
* size a single page can hold, which is the maximum page size
|
||||
* minus the padding.
|
||||
*
|
||||
* \sa RectPacker::RectPacker()
|
||||
*/
|
||||
rectTooBig
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
// A note on the implementation.
|
||||
// The current algorithm is absolutely the same as in version 1.0.0,
|
||||
// except that we only keep the leaf nodes of the binary tree. This
|
||||
// dramatically improves performance and reduces memory usage, but
|
||||
// growDown() and growRight() methods are harder to understand
|
||||
// because the leafs insertion order depends on several layers of
|
||||
// parent branches that don't physically exist. I added comments to
|
||||
// help you visualize what happens, but it will probably be easier
|
||||
// to just look at the code of the version 1.0.0.
|
||||
|
||||
|
||||
/**
|
||||
* Rectangle packer.
|
||||
*
|
||||
* GeomT is not required to hold negative numbers, and thus can be
|
||||
* an unsigned integer. It's also possible to use a floating-point
|
||||
* or a custom numeric type.
|
||||
*
|
||||
* A custom type for GeomT should support:
|
||||
* * Implicit construction from an integer >= 0
|
||||
* * Addition and subtraction (including compound assignment)
|
||||
* * Comparison
|
||||
*
|
||||
* \tparam GeomT numeric type to use for geometry
|
||||
*/
|
||||
template<typename GeomT = int>
|
||||
class RectPacker {
|
||||
public:
|
||||
struct Spacing {
|
||||
GeomT x; ///< Horizontal spacing
|
||||
GeomT y; ///< Vertical spacing
|
||||
|
||||
/**
|
||||
* Construct Spacing with the same spacing for both dimensions.
|
||||
*/
|
||||
explicit Spacing(GeomT spacing)
|
||||
: x(spacing)
|
||||
, y(spacing)
|
||||
{}
|
||||
|
||||
Spacing(GeomT x, GeomT y)
|
||||
: x(x)
|
||||
, y(y)
|
||||
{}
|
||||
};
|
||||
|
||||
struct Padding {
|
||||
GeomT top;
|
||||
GeomT bottom;
|
||||
GeomT left;
|
||||
GeomT right;
|
||||
|
||||
/**
|
||||
* Construct Padding with the same padding for all sides.
|
||||
*/
|
||||
explicit Padding(GeomT padding)
|
||||
: top(padding)
|
||||
, bottom(padding)
|
||||
, left(padding)
|
||||
, right(padding)
|
||||
{}
|
||||
|
||||
Padding(GeomT top, GeomT bottom, GeomT left, GeomT right)
|
||||
: top(top)
|
||||
, bottom(bottom)
|
||||
, left(left)
|
||||
, right(right)
|
||||
{}
|
||||
};
|
||||
|
||||
struct Position {
|
||||
GeomT x;
|
||||
GeomT y;
|
||||
|
||||
Position()
|
||||
: x()
|
||||
, y()
|
||||
{}
|
||||
|
||||
Position(GeomT x, GeomT y)
|
||||
: x(x)
|
||||
, y(y)
|
||||
{}
|
||||
};
|
||||
|
||||
/**
|
||||
* Result returned by RectPacker::insert().
|
||||
*/
|
||||
struct InsertResult {
|
||||
/**
|
||||
* Status of the insertion.
|
||||
*
|
||||
* \warning If InsertResult.status is not InsertStatus::ok,
|
||||
* values of all other fields of InsertResult are undefined.
|
||||
*/
|
||||
InsertStatus::Type status;
|
||||
|
||||
/**
|
||||
* Position of the inserted rectangle within the page.
|
||||
*/
|
||||
Position pos;
|
||||
|
||||
/**
|
||||
* Index of the page in which the rectangle was inserted.
|
||||
*
|
||||
* \sa getPageSize()
|
||||
*/
|
||||
std::size_t pageIndex;
|
||||
};
|
||||
|
||||
/**
|
||||
* RectPacker constructor.
|
||||
*
|
||||
* maxPageWidth and maxPageHeight define the maximum size of
|
||||
* a single page, including the padding. Depending on this limit
|
||||
* and the features of GeomT, a RectPacker can work in multipage
|
||||
* or infinite single-page mode.
|
||||
*
|
||||
* To enable infinite single-page mode, you have two choices,
|
||||
* depending on the properties of GeomT:
|
||||
* * If GeomT has a physical limit (like any standard integer),
|
||||
* you can set the maximum size to the maximum positive
|
||||
* value GeomT can hold.
|
||||
* * Otherwise, if GeomT is a floating-point type or a custom
|
||||
* unbounded type, you can set the maximum size to a huge
|
||||
* value or, if supported by the type, a magic value that
|
||||
* always bigger than any finite number (like a positive
|
||||
* infinity for floating-point types).
|
||||
*
|
||||
* If GeomT can hold negative values, the maximum page size, spacing,
|
||||
* and padding will be clamped to 0. Keep in mind that if the
|
||||
* maximum page size is 0, or if the total padding greater or equal
|
||||
* to the maximum page size, pages will have no free space for
|
||||
* rectangles, and all calls to insert() will result in
|
||||
* InsertStatus::rectTooBig.
|
||||
*
|
||||
* \param maxPageWidth maximum width of a page, including
|
||||
* the horizontal padding
|
||||
* \param maxPageHeight maximum height of a page, including
|
||||
* the vertical padding
|
||||
* \param rectsSpacing space between rectangles
|
||||
* \param pagePadding space between rectangles and edges of a page
|
||||
*/
|
||||
RectPacker(
|
||||
GeomT maxPageWidth, GeomT maxPageHeight,
|
||||
const Spacing& rectsSpacing = Spacing(0),
|
||||
const Padding& pagePadding = Padding(0))
|
||||
: ctx(maxPageWidth, maxPageHeight, rectsSpacing, pagePadding)
|
||||
, pages(1)
|
||||
{}
|
||||
|
||||
/**
|
||||
* Return the current number of pages.
|
||||
*
|
||||
* \returns number of pages (always > 0)
|
||||
*/
|
||||
std::size_t getNumPages() const
|
||||
{
|
||||
return pages.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current size of the page.
|
||||
*
|
||||
* \param pageIndex index of the page in range [0..getNumPages())
|
||||
* \param[out] width width of the page
|
||||
* \param[out] height height of the page
|
||||
*
|
||||
* \sa getNumPages(), InsertResult::pageIndex
|
||||
*/
|
||||
void getPageSize(std::size_t pageIndex, GeomT& width, GeomT& height) const
|
||||
{
|
||||
const Size size = pages[pageIndex].getSize(ctx);
|
||||
width = size.w;
|
||||
height = size.h;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a rectangle.
|
||||
*
|
||||
* The rectangles you'll feed to insert() should be sorted in
|
||||
* descending order by comparing first by height, then by width.
|
||||
* A comparison function for std::sort may look like the following:
|
||||
* \code
|
||||
* bool compare(const T& a, const T& b)
|
||||
* {
|
||||
* if (a.height != b.height)
|
||||
* return a.height > b.height;
|
||||
* else
|
||||
* return a.width > b.width;
|
||||
* }
|
||||
* \endcode
|
||||
*
|
||||
* \param width width of the rectangle
|
||||
* \param height height of the rectangle
|
||||
* \returns InsertResult
|
||||
*/
|
||||
InsertResult insert(GeomT width, GeomT height);
|
||||
private:
|
||||
struct Size {
|
||||
GeomT w;
|
||||
GeomT h;
|
||||
|
||||
Size(GeomT w, GeomT h)
|
||||
: w(w)
|
||||
, h(h)
|
||||
{}
|
||||
};
|
||||
|
||||
struct Context;
|
||||
class Page {
|
||||
public:
|
||||
Page()
|
||||
: nodes()
|
||||
, rootSize(0, 0)
|
||||
, growDownRootBottomIdx(0)
|
||||
{}
|
||||
|
||||
Size getSize(const Context& ctx) const
|
||||
{
|
||||
return Size(
|
||||
ctx.padding.left + rootSize.w + ctx.padding.right,
|
||||
ctx.padding.top + rootSize.h + ctx.padding.bottom);
|
||||
}
|
||||
|
||||
bool insert(Context& ctx, const Size& rect, Position& pos);
|
||||
private:
|
||||
struct Node {
|
||||
Position pos;
|
||||
Size size;
|
||||
|
||||
Node(GeomT x, GeomT y, GeomT w, GeomT h)
|
||||
: pos(x, y)
|
||||
, size(w, h)
|
||||
{}
|
||||
};
|
||||
|
||||
// Leaf nodes of the binary tree in depth-first order
|
||||
std::vector<Node> nodes;
|
||||
Size rootSize;
|
||||
// The index of the first leaf bottom node of the new root
|
||||
// created in growDown(). See the method for more details.
|
||||
std::size_t growDownRootBottomIdx;
|
||||
|
||||
bool tryInsert(Context& ctx, const Size& rect, Position& pos);
|
||||
bool findNode(
|
||||
const Size& rect,
|
||||
std::size_t& nodeIdx, Position& pos) const;
|
||||
void subdivideNode(
|
||||
Context& ctx, std::size_t nodeIdx, const Size& rect);
|
||||
bool tryGrow(Context& ctx, const Size& rect, Position& pos);
|
||||
void growDown(Context& ctx, const Size& rect, Position& pos);
|
||||
void growRight(Context& ctx, const Size& rect, Position& pos);
|
||||
};
|
||||
|
||||
struct Context {
|
||||
Size maxSize;
|
||||
Spacing spacing;
|
||||
Padding padding;
|
||||
|
||||
Context(
|
||||
GeomT maxPageWidth, GeomT maxPageHeight,
|
||||
const Spacing& rectsSpacing, const Padding& pagePadding);
|
||||
|
||||
static void subtractPadding(GeomT& padding, GeomT& size);
|
||||
};
|
||||
|
||||
Context ctx;
|
||||
std::vector<Page> pages;
|
||||
};
|
||||
|
||||
|
||||
template<typename GeomT>
|
||||
typename RectPacker<GeomT>::InsertResult
|
||||
RectPacker<GeomT>::insert(GeomT width, GeomT height)
|
||||
{
|
||||
InsertResult result;
|
||||
|
||||
if (width < 0 || height < 0) {
|
||||
result.status = InsertStatus::negativeSize;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (width == 0 || height == 0) {
|
||||
result.status = InsertStatus::zeroSize;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (width > ctx.maxSize.w || height > ctx.maxSize.h) {
|
||||
result.status = InsertStatus::rectTooBig;
|
||||
return result;
|
||||
}
|
||||
|
||||
const Size rect(width, height);
|
||||
|
||||
for (std::size_t i = 0; i < pages.size(); ++i)
|
||||
if (pages[i].insert(ctx, rect, result.pos)) {
|
||||
result.status = InsertStatus::ok;
|
||||
result.pageIndex = i;
|
||||
return result;
|
||||
}
|
||||
|
||||
pages.push_back(Page());
|
||||
Page& page = pages.back();
|
||||
page.insert(ctx, rect, result.pos);
|
||||
result.status = InsertStatus::ok;
|
||||
result.pageIndex = pages.size() - 1;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
template<typename GeomT>
|
||||
bool RectPacker<GeomT>::Page::insert(
|
||||
Context& ctx, const Size& rect, Position& pos)
|
||||
{
|
||||
assert(rect.w > 0);
|
||||
assert(rect.w <= ctx.maxSize.w);
|
||||
assert(rect.h > 0);
|
||||
assert(rect.h <= ctx.maxSize.h);
|
||||
|
||||
// The first insertion should be handled especially since
|
||||
// growRight() and growDown() add spacing between the root
|
||||
// and the inserted rectangle.
|
||||
if (rootSize.w == 0) {
|
||||
rootSize = rect;
|
||||
pos.x = ctx.padding.left;
|
||||
pos.y = ctx.padding.top;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return tryInsert(ctx, rect, pos) || tryGrow(ctx, rect, pos);
|
||||
}
|
||||
|
||||
|
||||
template<typename GeomT>
|
||||
bool RectPacker<GeomT>::Page::tryInsert(
|
||||
Context& ctx, const Size& rect, Position& pos)
|
||||
{
|
||||
std::size_t nodeIdx;
|
||||
if (findNode(rect, nodeIdx, pos)) {
|
||||
subdivideNode(ctx, nodeIdx, rect);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
template<typename GeomT>
|
||||
bool RectPacker<GeomT>::Page::findNode(
|
||||
const Size& rect, std::size_t& nodeIdx, Position& pos) const
|
||||
{
|
||||
for (nodeIdx = 0; nodeIdx < nodes.size(); ++nodeIdx) {
|
||||
const Node& node = nodes[nodeIdx];
|
||||
if (rect.w <= node.size.w && rect.h <= node.size.h) {
|
||||
pos = node.pos;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called after a rectangle was inserted in the top left corner of
|
||||
* a free node to create child nodes from free space, if any.
|
||||
*
|
||||
* The node is first cut horizontally along the rect's bottom,
|
||||
* then vertically along the right edge of the rect. Splitting
|
||||
* that way is crucial for the algorithm to work correctly.
|
||||
*
|
||||
* +---+
|
||||
* | |
|
||||
* +---+---+
|
||||
* | |
|
||||
* +-------+
|
||||
*/
|
||||
template<typename GeomT>
|
||||
void RectPacker<GeomT>::Page::subdivideNode(
|
||||
Context& ctx, std::size_t nodeIdx, const Size& rect)
|
||||
{
|
||||
assert(nodeIdx < nodes.size());
|
||||
|
||||
Node& node = nodes[nodeIdx];
|
||||
|
||||
assert(node.size.w >= rect.w);
|
||||
const GeomT rightW = node.size.w - rect.w;
|
||||
const bool hasSpaceRight = rightW > ctx.spacing.x;
|
||||
|
||||
assert(node.size.h >= rect.h);
|
||||
const GeomT bottomH = node.size.h - rect.h;
|
||||
const bool hasSpaceBelow = bottomH > ctx.spacing.y;
|
||||
|
||||
if (hasSpaceRight) {
|
||||
// Right node replaces the current
|
||||
|
||||
const GeomT bottomX = node.pos.x;
|
||||
const GeomT bottomW = node.size.w;
|
||||
|
||||
node.pos.x += rect.w + ctx.spacing.x;
|
||||
node.size.w = rightW - ctx.spacing.x;
|
||||
node.size.h = rect.h;
|
||||
|
||||
if (hasSpaceBelow) {
|
||||
nodes.insert(
|
||||
nodes.begin() + nodeIdx + 1,
|
||||
Node(
|
||||
bottomX,
|
||||
node.pos.y + rect.h + ctx.spacing.y,
|
||||
bottomW,
|
||||
bottomH - ctx.spacing.y));
|
||||
|
||||
if (nodeIdx <= growDownRootBottomIdx)
|
||||
++growDownRootBottomIdx;
|
||||
}
|
||||
} else if (hasSpaceBelow) {
|
||||
// Bottom node replaces the current
|
||||
node.pos.y += rect.h + ctx.spacing.y;
|
||||
node.size.h = bottomH - ctx.spacing.y;
|
||||
} else {
|
||||
nodes.erase(nodes.begin() + nodeIdx);
|
||||
if (nodeIdx < growDownRootBottomIdx)
|
||||
--growDownRootBottomIdx;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template<typename GeomT>
|
||||
bool RectPacker<GeomT>::Page::tryGrow(
|
||||
Context& ctx, const Size& rect, Position& pos)
|
||||
{
|
||||
assert(ctx.maxSize.w >= rootSize.w);
|
||||
const GeomT freeW = ctx.maxSize.w - rootSize.w;
|
||||
assert(ctx.maxSize.h >= rootSize.h);
|
||||
const GeomT freeH = ctx.maxSize.h - rootSize.h;
|
||||
|
||||
const bool canGrowDown = (
|
||||
freeH >= rect.h && freeH - rect.h >= ctx.spacing.y);
|
||||
const bool mustGrowDown = (
|
||||
canGrowDown
|
||||
&& freeW >= ctx.spacing.x
|
||||
&& (rootSize.w + ctx.spacing.x
|
||||
>= rootSize.h + rect.h + ctx.spacing.y));
|
||||
if (mustGrowDown) {
|
||||
growDown(ctx, rect, pos);
|
||||
return true;
|
||||
}
|
||||
|
||||
const bool canGrowRight = (
|
||||
freeW >= rect.w && freeW - rect.w >= ctx.spacing.x);
|
||||
if (canGrowRight) {
|
||||
growRight(ctx, rect, pos);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (canGrowDown) {
|
||||
growDown(ctx, rect, pos);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
template<typename GeomT>
|
||||
void RectPacker<GeomT>::Page::growDown(
|
||||
Context& ctx, const Size& rect, Position& pos)
|
||||
{
|
||||
assert(ctx.maxSize.h > rootSize.h);
|
||||
assert(ctx.maxSize.h - rootSize.h >= rect.h);
|
||||
assert(ctx.maxSize.h - rootSize.h - rect.h >= ctx.spacing.y);
|
||||
|
||||
pos.x = ctx.padding.left;
|
||||
pos.y = ctx.padding.top + rootSize.h + ctx.spacing.y;
|
||||
|
||||
if (rootSize.w < rect.w) {
|
||||
if (rect.w - rootSize.w > ctx.spacing.x) {
|
||||
// The auxiliary node becomes the right child of the new
|
||||
// root. It contains the current root (bottom child) and
|
||||
// free space at the current root's right (right child).
|
||||
nodes.insert(
|
||||
nodes.begin(),
|
||||
Node(
|
||||
ctx.padding.left + rootSize.w + ctx.spacing.x,
|
||||
ctx.padding.top,
|
||||
rect.w - rootSize.w - ctx.spacing.x,
|
||||
rootSize.h));
|
||||
++growDownRootBottomIdx;
|
||||
}
|
||||
|
||||
rootSize.w = rect.w;
|
||||
} else if (rootSize.w - rect.w > ctx.spacing.x) {
|
||||
// Free space at the right of the inserted rect becomes the
|
||||
// right child of the rect's node, which in turn is the
|
||||
// bottom child of the new root.
|
||||
nodes.insert(
|
||||
nodes.begin() + growDownRootBottomIdx,
|
||||
Node(
|
||||
pos.x + rect.w + ctx.spacing.x,
|
||||
pos.y,
|
||||
rootSize.w - rect.w - ctx.spacing.x,
|
||||
rect.h));
|
||||
|
||||
// The inserted node is visited before the node from the next
|
||||
// growDown() since the current new root will be the right
|
||||
// child of the next root.
|
||||
++growDownRootBottomIdx;
|
||||
}
|
||||
|
||||
rootSize.h += ctx.spacing.y + rect.h;
|
||||
}
|
||||
|
||||
|
||||
template<typename GeomT>
|
||||
void RectPacker<GeomT>::Page::growRight(
|
||||
Context& ctx, const Size& rect, Position& pos)
|
||||
{
|
||||
assert(ctx.maxSize.w > rootSize.w);
|
||||
assert(ctx.maxSize.w - rootSize.w >= rect.w);
|
||||
assert(ctx.maxSize.w - rootSize.w - rect.w >= ctx.spacing.x);
|
||||
|
||||
pos.x = ctx.padding.left + rootSize.w + ctx.spacing.x;
|
||||
pos.y = ctx.padding.top;
|
||||
|
||||
if (rootSize.h < rect.h) {
|
||||
if (rect.h - rootSize.h > ctx.spacing.y)
|
||||
// The auxiliary node becomes the bottom child of the
|
||||
// new root. It contains the current root (right child)
|
||||
// and free space at the current root's bottom, if any
|
||||
// (bottom child).
|
||||
nodes.insert(
|
||||
nodes.end(),
|
||||
Node(
|
||||
ctx.padding.left,
|
||||
ctx.padding.top + rootSize.h + ctx.spacing.y,
|
||||
rootSize.w,
|
||||
rect.h - rootSize.h - ctx.spacing.y));
|
||||
|
||||
rootSize.h = rect.h;
|
||||
} else if (rootSize.h - rect.h > ctx.spacing.y) {
|
||||
// Free space at the bottom of the inserted rect becomes the
|
||||
// bottom child of the rect's node, which in turn is the
|
||||
// right child of the new root node.
|
||||
nodes.insert(
|
||||
nodes.begin(),
|
||||
Node(
|
||||
pos.x,
|
||||
pos.y + rect.h + ctx.spacing.y,
|
||||
rect.w,
|
||||
rootSize.h - rect.h - ctx.spacing.y));
|
||||
++growDownRootBottomIdx;
|
||||
}
|
||||
|
||||
rootSize.w += ctx.spacing.x + rect.w;
|
||||
}
|
||||
|
||||
|
||||
template<typename GeomT>
|
||||
RectPacker<GeomT>::Context::Context(
|
||||
GeomT maxPageWidth, GeomT maxPageHeight,
|
||||
const Spacing& rectsSpacing, const Padding& pagePadding)
|
||||
: maxSize(maxPageWidth, maxPageHeight)
|
||||
, spacing(rectsSpacing)
|
||||
, padding(pagePadding)
|
||||
{
|
||||
if (maxSize.w < 0)
|
||||
maxSize.w = 0;
|
||||
if (maxSize.h < 0)
|
||||
maxSize.h = 0;
|
||||
|
||||
if (spacing.x < 0)
|
||||
spacing.x = 0;
|
||||
if (spacing.y < 0)
|
||||
spacing.y = 0;
|
||||
|
||||
subtractPadding(padding.top, maxSize.h);
|
||||
subtractPadding(padding.bottom, maxSize.h);
|
||||
subtractPadding(padding.left, maxSize.w);
|
||||
subtractPadding(padding.right, maxSize.w);
|
||||
}
|
||||
|
||||
|
||||
template<typename GeomT>
|
||||
void RectPacker<GeomT>::Context::subtractPadding(
|
||||
GeomT& padding, GeomT& size)
|
||||
{
|
||||
if (padding < 0)
|
||||
padding = 0;
|
||||
else if (padding < size)
|
||||
size -= padding;
|
||||
else {
|
||||
padding = size;
|
||||
size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace rect_pack
|
||||
} // namespace dp
|
||||
|
||||
|
||||
#endif // DP_RECT_PACK_H
|
|
@ -762,6 +762,8 @@ set (VULKAN_SOURCES
|
|||
common/rendering/vulkan/textures/vk_texture.cpp
|
||||
common/rendering/vulkan/framebuffers/vk_framebuffer.cpp
|
||||
common/rendering/vulkan/accelstructs/vk_raytrace.cpp
|
||||
common/rendering/vulkan/accelstructs/vk_lightmap.cpp
|
||||
common/rendering/vulkan/accelstructs/halffloat.cpp
|
||||
)
|
||||
|
||||
if (HAVE_VULKAN)
|
||||
|
@ -1279,6 +1281,7 @@ include_directories(
|
|||
scripting/zscript
|
||||
rendering
|
||||
../libraries/ZVulkan/include
|
||||
../libraries/dp_rect_pack
|
||||
${SYSTEM_SOURCES_DIR}
|
||||
)
|
||||
|
||||
|
|
|
@ -4,10 +4,82 @@
|
|||
#include "tarray.h"
|
||||
#include "vectors.h"
|
||||
#include "hw_collision.h"
|
||||
#include <memory>
|
||||
|
||||
namespace hwrenderer
|
||||
{
|
||||
|
||||
// Note: this is just the current layout needed to get VkLightmap/GPURaytracer from zdray to compile within this project.
|
||||
//
|
||||
// The surface actually class needs to be moved up DoomLevelMesh, since this can't otherwise be accessed in the common part of the codebase shared with Raze.
|
||||
// Ideally, we'd undoomify it as much as possible, so Raze in theory also would be able to declare an raytracing acceleration structure for dynlights and lightmaps
|
||||
|
||||
class ThingLight
|
||||
{
|
||||
public:
|
||||
FVector3 Origin;
|
||||
FVector3 RelativeOrigin;
|
||||
float Radius;
|
||||
float Intensity;
|
||||
float InnerAngleCos;
|
||||
float OuterAngleCos;
|
||||
FVector3 SpotDir;
|
||||
FVector3 Color;
|
||||
};
|
||||
|
||||
enum SurfaceType
|
||||
{
|
||||
ST_UNKNOWN,
|
||||
ST_MIDDLESIDE,
|
||||
ST_UPPERSIDE,
|
||||
ST_LOWERSIDE,
|
||||
ST_CEILING,
|
||||
ST_FLOOR
|
||||
};
|
||||
|
||||
class Surface
|
||||
{
|
||||
public:
|
||||
// Surface geometry
|
||||
SurfaceType type = ST_UNKNOWN;
|
||||
TArray<FVector3> verts;
|
||||
//Plane plane;
|
||||
FVector3 boundsMin, boundsMax;
|
||||
|
||||
// Touching light sources
|
||||
std::vector<ThingLight*> LightList;
|
||||
|
||||
// Lightmap world coordinates for the texture
|
||||
FVector3 worldOrigin = { 0.0f, 0.0f, 0.0f };
|
||||
FVector3 worldStepX = { 0.0f, 0.0f, 0.0f };
|
||||
FVector3 worldStepY = { 0.0f, 0.0f, 0.0f };
|
||||
|
||||
// Calculate world coordinates to UV coordinates
|
||||
FVector3 translateWorldToLocal = { 0.0f, 0.0f, 0.0f };
|
||||
FVector3 projLocalToU = { 0.0f, 0.0f, 0.0f };
|
||||
FVector3 projLocalToV = { 0.0f, 0.0f, 0.0f };
|
||||
|
||||
// Output lightmap for the surface
|
||||
int texWidth = 0;
|
||||
int texHeight = 0;
|
||||
std::vector<FVector3> texPixels;
|
||||
|
||||
// Placement in final texture atlas
|
||||
int atlasPageIndex = -1;
|
||||
int atlasX = 0;
|
||||
int atlasY = 0;
|
||||
|
||||
// Smoothing group surface is to be rendered with
|
||||
int smoothingGroupIndex = -1;
|
||||
};
|
||||
|
||||
struct SmoothingGroup
|
||||
{
|
||||
FVector4 plane = FVector4(0, 0, 1, 0);
|
||||
int sectorGroup = 0;
|
||||
std::vector<Surface*> surfaces;
|
||||
};
|
||||
|
||||
class LevelMesh
|
||||
{
|
||||
public:
|
||||
|
@ -20,6 +92,12 @@ public:
|
|||
|
||||
std::unique_ptr<TriangleMeshShape> Collision;
|
||||
|
||||
// To do: these are currently not filled
|
||||
TArray<std::unique_ptr<Surface>> surfaces;
|
||||
TArray<SmoothingGroup> smoothingGroups;
|
||||
FVector3 SunDirection;
|
||||
FVector3 SunColor;
|
||||
|
||||
bool Trace(const FVector3& start, FVector3 direction, float maxDist)
|
||||
{
|
||||
FVector3 end = start + direction * std::max(maxDist - 10.0f, 0.0f);
|
||||
|
|
3381
src/common/rendering/vulkan/accelstructs/halffloat.cpp
Normal file
3381
src/common/rendering/vulkan/accelstructs/halffloat.cpp
Normal file
File diff suppressed because it is too large
Load diff
68
src/common/rendering/vulkan/accelstructs/halffloat.h
Normal file
68
src/common/rendering/vulkan/accelstructs/halffloat.h
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
**
|
||||
** This software is provided 'as-is', without any express or implied
|
||||
** warranty. In no event will the authors be held liable for any damages
|
||||
** arising from the use of this software.
|
||||
**
|
||||
** Permission is granted to anyone to use this software for any purpose,
|
||||
** including commercial applications, and to alter it and redistribute it
|
||||
** freely, subject to the following restrictions:
|
||||
**
|
||||
** 1. The origin of this software must not be misrepresented; you must not
|
||||
** claim that you wrote the original software. If you use this software
|
||||
** in a product, an acknowledgment in the product documentation would be
|
||||
** appreciated but is not required.
|
||||
** 2. Altered source versions must be plainly marked as such, and must not be
|
||||
** misrepresented as being the original software.
|
||||
** 3. This notice may not be removed or altered from any source distribution.
|
||||
**
|
||||
** Note: Some of the libraries UICore may link to may have additional
|
||||
** requirements or restrictions.
|
||||
**
|
||||
** Based on the paper "Fast Half Float Conversions" by Jeroen van der Zijp.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace HalfFloatTables
|
||||
{
|
||||
extern unsigned int mantissa_table[2048];
|
||||
extern unsigned int exponent_table[64];
|
||||
extern unsigned short offset_table[64];
|
||||
extern unsigned short base_table[512];
|
||||
extern unsigned char shift_table[512];
|
||||
};
|
||||
|
||||
/// Convert half-float to float. Only works for 'normal' half-float values
|
||||
inline float halfToFloatSimple(unsigned short hf)
|
||||
{
|
||||
unsigned int float_value = ((hf & 0x8000) << 16) | (((hf & 0x7c00) + 0x1C000) << 13) | ((hf & 0x03FF) << 13);
|
||||
void *ptr = static_cast<void*>(&float_value);
|
||||
return *static_cast<float*>(ptr);
|
||||
}
|
||||
|
||||
/// Convert float to half-float. Only works for 'normal' half-float values
|
||||
inline unsigned short floatToHalfSimple(float float_value)
|
||||
{
|
||||
void *ptr = static_cast<void*>(&float_value);
|
||||
unsigned int f = *static_cast<unsigned int*>(ptr);
|
||||
return ((f >> 16) & 0x8000) | ((((f & 0x7f800000) - 0x38000000) >> 13) & 0x7c00) | ((f >> 13) & 0x03ff);
|
||||
}
|
||||
|
||||
/// Convert half-float to float
|
||||
inline float halfToFloat(unsigned short hf)
|
||||
{
|
||||
using namespace HalfFloatTables;
|
||||
unsigned int float_value = mantissa_table[offset_table[hf >> 10] + (hf & 0x3ff)] + exponent_table[hf >> 10];
|
||||
void *ptr = static_cast<void*>(&float_value);
|
||||
return *static_cast<float*>(ptr);
|
||||
}
|
||||
|
||||
/// Convert float to half-float
|
||||
inline unsigned short floatToHalf(float float_value)
|
||||
{
|
||||
using namespace HalfFloatTables;
|
||||
void *ptr = static_cast<void*>(&float_value);
|
||||
unsigned int f = *static_cast<unsigned int*>(ptr);
|
||||
return base_table[(f >> 23) & 0x1ff] + ((f & 0x007fffff) >> shift_table[(f >> 23) & 0x1ff]);
|
||||
}
|
669
src/common/rendering/vulkan/accelstructs/vk_lightmap.cpp
Normal file
669
src/common/rendering/vulkan/accelstructs/vk_lightmap.cpp
Normal file
|
@ -0,0 +1,669 @@
|
|||
|
||||
#include "vk_lightmap.h"
|
||||
#include "vulkan/vk_renderdevice.h"
|
||||
#include "zvulkan/vulkanbuilders.h"
|
||||
#include "halffloat.h"
|
||||
#include "filesystem.h"
|
||||
|
||||
VkLightmap::VkLightmap(VulkanRenderDevice* fb) : fb(fb)
|
||||
{
|
||||
useRayQuery = fb->GetDevice()->PhysicalDevice.Features.RayQuery.rayQuery;
|
||||
|
||||
submitFence = std::make_unique<VulkanFence>(fb->GetDevice());
|
||||
cmdpool = std::make_unique<VulkanCommandPool>(fb->GetDevice(), fb->GetDevice()->GraphicsFamily);
|
||||
|
||||
CreateUniformBuffer();
|
||||
CreateSceneVertexBuffer();
|
||||
CreateSceneLightBuffer();
|
||||
|
||||
CreateShaders();
|
||||
CreateRaytracePipeline();
|
||||
CreateResolvePipeline();
|
||||
}
|
||||
|
||||
VkLightmap::~VkLightmap()
|
||||
{
|
||||
}
|
||||
|
||||
void VkLightmap::Raytrace(hwrenderer::LevelMesh* level)
|
||||
{
|
||||
mesh = level;
|
||||
|
||||
UpdateAccelStructDescriptors(); // To do: we only need to do this if the accel struct changes.
|
||||
|
||||
BeginCommands();
|
||||
UploadUniforms();
|
||||
|
||||
for (size_t pageIndex = 0; pageIndex < atlasImages.size(); pageIndex++)
|
||||
{
|
||||
RenderAtlasImage(pageIndex);
|
||||
}
|
||||
|
||||
for (size_t pageIndex = 0; pageIndex < atlasImages.size(); pageIndex++)
|
||||
{
|
||||
ResolveAtlasImage(pageIndex);
|
||||
}
|
||||
|
||||
FinishCommands();
|
||||
}
|
||||
|
||||
void VkLightmap::BeginCommands()
|
||||
{
|
||||
cmdbuffer = cmdpool->createBuffer();
|
||||
cmdbuffer->begin();
|
||||
}
|
||||
|
||||
void VkLightmap::FinishCommands()
|
||||
{
|
||||
cmdbuffer->end();
|
||||
|
||||
QueueSubmit()
|
||||
.AddCommandBuffer(cmdbuffer.get())
|
||||
.Execute(fb->GetDevice(), fb->GetDevice()->GraphicsQueue, submitFence.get());
|
||||
|
||||
VkResult result = vkWaitForFences(fb->GetDevice()->device, 1, &submitFence->fence, VK_TRUE, std::numeric_limits<uint64_t>::max());
|
||||
if (result != VK_SUCCESS)
|
||||
throw std::runtime_error("vkWaitForFences failed");
|
||||
result = vkResetFences(fb->GetDevice()->device, 1, &submitFence->fence);
|
||||
if (result != VK_SUCCESS)
|
||||
throw std::runtime_error("vkResetFences failed");
|
||||
cmdbuffer.reset();
|
||||
}
|
||||
|
||||
void VkLightmap::RenderAtlasImage(size_t pageIndex)
|
||||
{
|
||||
LightmapImage& img = atlasImages[pageIndex];
|
||||
|
||||
const auto beginPass = [&]() {
|
||||
RenderPassBegin()
|
||||
.RenderPass(raytrace.renderPass.get())
|
||||
.RenderArea(0, 0, atlasImageSize, atlasImageSize)
|
||||
.Framebuffer(img.raytrace.Framebuffer.get())
|
||||
.Execute(cmdbuffer.get());
|
||||
|
||||
VkDeviceSize offset = 0;
|
||||
cmdbuffer->bindVertexBuffers(0, 1, &sceneVertexBuffer->buffer, &offset);
|
||||
cmdbuffer->bindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, raytrace.pipeline.get());
|
||||
cmdbuffer->bindDescriptorSet(VK_PIPELINE_BIND_POINT_GRAPHICS, raytrace.pipelineLayout.get(), 0, raytrace.descriptorSet0.get());
|
||||
cmdbuffer->bindDescriptorSet(VK_PIPELINE_BIND_POINT_GRAPHICS, raytrace.pipelineLayout.get(), 1, raytrace.descriptorSet1.get());
|
||||
};
|
||||
beginPass();
|
||||
|
||||
for (size_t i = 0; i < mesh->surfaces.Size(); i++)
|
||||
{
|
||||
hwrenderer::Surface* targetSurface = mesh->surfaces[i].get();
|
||||
if (targetSurface->atlasPageIndex != pageIndex)
|
||||
continue;
|
||||
|
||||
VkViewport viewport = {};
|
||||
viewport.maxDepth = 1;
|
||||
viewport.x = (float)targetSurface->atlasX - 1;
|
||||
viewport.y = (float)targetSurface->atlasY - 1;
|
||||
viewport.width = (float)(targetSurface->texWidth + 2);
|
||||
viewport.height = (float)(targetSurface->texHeight + 2);
|
||||
cmdbuffer->setViewport(0, 1, &viewport);
|
||||
|
||||
// Paint all surfaces part of the smoothing group into the surface
|
||||
for (hwrenderer::Surface* surface : mesh->smoothingGroups[targetSurface->smoothingGroupIndex].surfaces)
|
||||
{
|
||||
FVector2 minUV = ToUV(surface->boundsMin, targetSurface);
|
||||
FVector2 maxUV = ToUV(surface->boundsMax, targetSurface);
|
||||
if (surface != targetSurface && (maxUV.X < 0.0f || maxUV.Y < 0.0f || minUV.X > 1.0f || minUV.Y > 1.0f))
|
||||
continue; // Bounding box not visible
|
||||
|
||||
int firstLight = sceneLightPos;
|
||||
int firstVertex = sceneVertexPos;
|
||||
int lightCount = (int)surface->LightList.size();
|
||||
int vertexCount = (int)surface->verts.Size();
|
||||
if (sceneLightPos + lightCount > SceneLightBufferSize || sceneVertexPos + vertexCount > SceneVertexBufferSize)
|
||||
{
|
||||
printf(".");
|
||||
|
||||
// Flush scene buffers
|
||||
FinishCommands();
|
||||
sceneLightPos = 0;
|
||||
sceneVertexPos = 0;
|
||||
firstLight = 0;
|
||||
firstVertex = 0;
|
||||
BeginCommands();
|
||||
beginPass();
|
||||
|
||||
printf(".");
|
||||
|
||||
if (sceneLightPos + lightCount > SceneLightBufferSize)
|
||||
{
|
||||
throw std::runtime_error("SceneLightBuffer is too small!");
|
||||
}
|
||||
else if (sceneVertexPos + vertexCount > SceneVertexBufferSize)
|
||||
{
|
||||
throw std::runtime_error("SceneVertexBuffer is too small!");
|
||||
}
|
||||
}
|
||||
sceneLightPos += lightCount;
|
||||
sceneVertexPos += vertexCount;
|
||||
|
||||
LightInfo* lightinfo = &sceneLights[firstLight];
|
||||
for (hwrenderer::ThingLight* light : surface->LightList)
|
||||
{
|
||||
lightinfo->Origin = light->Origin;
|
||||
lightinfo->RelativeOrigin = light->RelativeOrigin;
|
||||
lightinfo->Radius = light->Radius;
|
||||
lightinfo->Intensity = light->Intensity;
|
||||
lightinfo->InnerAngleCos = light->InnerAngleCos;
|
||||
lightinfo->OuterAngleCos = light->OuterAngleCos;
|
||||
lightinfo->SpotDir = light->SpotDir;
|
||||
lightinfo->Color = light->Color;
|
||||
lightinfo++;
|
||||
}
|
||||
|
||||
PushConstants pc;
|
||||
pc.LightStart = firstLight;
|
||||
pc.LightEnd = firstLight + lightCount;
|
||||
pc.SurfaceIndex = (int32_t)i;
|
||||
pc.LightmapOrigin = targetSurface->worldOrigin - targetSurface->worldStepX - targetSurface->worldStepY;
|
||||
pc.LightmapStepX = targetSurface->worldStepX * viewport.width;
|
||||
pc.LightmapStepY = targetSurface->worldStepY * viewport.height;
|
||||
cmdbuffer->pushConstants(raytrace.pipelineLayout.get(), VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstants), &pc);
|
||||
|
||||
SceneVertex* vertex = &sceneVertices[firstVertex];
|
||||
|
||||
if (surface->type == hwrenderer::ST_FLOOR || surface->type == hwrenderer::ST_CEILING)
|
||||
{
|
||||
for (int idx = 0; idx < vertexCount; idx++)
|
||||
{
|
||||
(vertex++)->Position = ToUV(surface->verts[idx], targetSurface);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
(vertex++)->Position = ToUV(surface->verts[0], targetSurface);
|
||||
(vertex++)->Position = ToUV(surface->verts[2], targetSurface);
|
||||
(vertex++)->Position = ToUV(surface->verts[3], targetSurface);
|
||||
(vertex++)->Position = ToUV(surface->verts[1], targetSurface);
|
||||
}
|
||||
|
||||
cmdbuffer->draw(vertexCount, 1, firstVertex, 0);
|
||||
}
|
||||
}
|
||||
|
||||
cmdbuffer->endRenderPass();
|
||||
}
|
||||
|
||||
void VkLightmap::CreateAtlasImages()
|
||||
{
|
||||
const int spacing = 3; // Note: the spacing is here to avoid that the resolve sampler finds data from other surface tiles
|
||||
RectPacker packer(atlasImageSize, atlasImageSize, RectPacker::Spacing(spacing));
|
||||
|
||||
for (size_t i = 0; i < mesh->surfaces.Size(); i++)
|
||||
{
|
||||
hwrenderer::Surface* surface = mesh->surfaces[i].get();
|
||||
|
||||
auto result = packer.insert(surface->texWidth + 2, surface->texHeight + 2);
|
||||
surface->atlasX = result.pos.x + 1;
|
||||
surface->atlasY = result.pos.y + 1;
|
||||
surface->atlasPageIndex = (int)result.pageIndex;
|
||||
}
|
||||
|
||||
for (size_t pageIndex = 0; pageIndex < packer.getNumPages(); pageIndex++)
|
||||
{
|
||||
atlasImages.push_back(CreateImage(atlasImageSize, atlasImageSize));
|
||||
}
|
||||
}
|
||||
|
||||
void VkLightmap::UploadUniforms()
|
||||
{
|
||||
Uniforms uniforms = {};
|
||||
uniforms.SunDir = mesh->SunDirection;
|
||||
uniforms.SunColor = mesh->SunColor;
|
||||
uniforms.SunIntensity = 1.0f;
|
||||
|
||||
mappedUniforms = (uint8_t*)uniformTransferBuffer->Map(0, uniformStructs * uniformStructStride);
|
||||
*reinterpret_cast<Uniforms*>(mappedUniforms + uniformStructStride * uniformsIndex) = uniforms;
|
||||
uniformTransferBuffer->Unmap();
|
||||
|
||||
cmdbuffer->copyBuffer(uniformTransferBuffer.get(), uniformBuffer.get());
|
||||
PipelineBarrier()
|
||||
.AddBuffer(uniformBuffer.get(), VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT)
|
||||
.Execute(cmdbuffer.get(), VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
|
||||
}
|
||||
|
||||
void VkLightmap::ResolveAtlasImage(size_t i)
|
||||
{
|
||||
LightmapImage& img = atlasImages[i];
|
||||
|
||||
PipelineBarrier()
|
||||
.AddImage(img.raytrace.Image.get(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT)
|
||||
.Execute(cmdbuffer.get(), VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
|
||||
|
||||
RenderPassBegin()
|
||||
.RenderPass(resolve.renderPass.get())
|
||||
.RenderArea(0, 0, atlasImageSize, atlasImageSize)
|
||||
.Framebuffer(img.resolve.Framebuffer.get())
|
||||
.Execute(cmdbuffer.get());
|
||||
|
||||
VkDeviceSize offset = 0;
|
||||
cmdbuffer->bindVertexBuffers(0, 1, &sceneVertexBuffer->buffer, &offset);
|
||||
cmdbuffer->bindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, resolve.pipeline.get());
|
||||
|
||||
auto descriptorSet = resolve.descriptorPool->allocate(resolve.descriptorSetLayout.get());
|
||||
descriptorSet->SetDebugName("resolve.descriptorSet");
|
||||
WriteDescriptors()
|
||||
.AddCombinedImageSampler(descriptorSet.get(), 0, img.raytrace.View.get(), resolve.sampler.get(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
.Execute(fb->GetDevice());
|
||||
cmdbuffer->bindDescriptorSet(VK_PIPELINE_BIND_POINT_GRAPHICS, resolve.pipelineLayout.get(), 0, descriptorSet.get());
|
||||
resolve.descriptorSets.push_back(std::move(descriptorSet));
|
||||
|
||||
VkViewport viewport = {};
|
||||
viewport.maxDepth = 1;
|
||||
viewport.width = (float)atlasImageSize;
|
||||
viewport.height = (float)atlasImageSize;
|
||||
cmdbuffer->setViewport(0, 1, &viewport);
|
||||
|
||||
PushConstants pc;
|
||||
pc.LightStart = 0;
|
||||
pc.LightEnd = 0;
|
||||
pc.SurfaceIndex = 0;
|
||||
pc.LightmapOrigin = FVector3(0.0f, 0.0f, 0.0f);
|
||||
pc.LightmapStepX = FVector3(0.0f, 0.0f, 0.0f);
|
||||
pc.LightmapStepY = FVector3(0.0f, 0.0f, 0.0f);
|
||||
cmdbuffer->pushConstants(resolve.pipelineLayout.get(), VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(PushConstants), &pc);
|
||||
|
||||
int firstVertex = sceneVertexPos;
|
||||
int vertexCount = 4;
|
||||
sceneVertexPos += vertexCount;
|
||||
SceneVertex* vertex = &sceneVertices[firstVertex];
|
||||
vertex[0].Position = FVector2(0.0f, 0.0f);
|
||||
vertex[1].Position = FVector2(1.0f, 0.0f);
|
||||
vertex[2].Position = FVector2(1.0f, 1.0f);
|
||||
vertex[3].Position = FVector2(0.0f, 1.0f);
|
||||
cmdbuffer->draw(vertexCount, 1, firstVertex, 0);
|
||||
|
||||
cmdbuffer->endRenderPass();
|
||||
|
||||
PipelineBarrier()
|
||||
.AddImage(img.resolve.Image.get(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT)
|
||||
.Execute(cmdbuffer.get(), VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
|
||||
|
||||
VkBufferImageCopy region = {};
|
||||
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
region.imageSubresource.layerCount = 1;
|
||||
region.imageExtent.width = atlasImageSize;
|
||||
region.imageExtent.height = atlasImageSize;
|
||||
region.imageExtent.depth = 1;
|
||||
cmdbuffer->copyImageToBuffer(img.resolve.Image->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, img.Transfer->buffer, 1, ®ion);
|
||||
}
|
||||
|
||||
void VkLightmap::DownloadAtlasImage(size_t pageIndex)
|
||||
{
|
||||
struct hvec4
|
||||
{
|
||||
unsigned short x, y, z, w;
|
||||
FVector3 xyz() { return FVector3(halfToFloat(x), halfToFloat(y), halfToFloat(z)); }
|
||||
};
|
||||
|
||||
hvec4* pixels = (hvec4*)atlasImages[pageIndex].Transfer->Map(0, atlasImageSize * atlasImageSize * sizeof(hvec4));
|
||||
|
||||
for (size_t i = 0; i < mesh->surfaces.Size(); i++)
|
||||
{
|
||||
hwrenderer::Surface* surface = mesh->surfaces[i].get();
|
||||
if (surface->atlasPageIndex != pageIndex)
|
||||
continue;
|
||||
|
||||
int atlasX = surface->atlasX;
|
||||
int atlasY = surface->atlasY;
|
||||
int sampleWidth = surface->texWidth;
|
||||
int sampleHeight = surface->texHeight;
|
||||
|
||||
for (int y = 0; y < sampleHeight; y++)
|
||||
{
|
||||
FVector3* dest = &surface->texPixels[y * sampleWidth];
|
||||
hvec4* src = &pixels[atlasX + (atlasY + y) * atlasImageSize];
|
||||
for (int x = 0; x < sampleWidth; x++)
|
||||
{
|
||||
dest[x] = src[x].xyz();
|
||||
}
|
||||
}
|
||||
}
|
||||
atlasImages[pageIndex].Transfer->Unmap();
|
||||
}
|
||||
|
||||
FVector2 VkLightmap::ToUV(const FVector3& vert, const hwrenderer::Surface* targetSurface)
|
||||
{
|
||||
FVector3 localPos = vert - targetSurface->translateWorldToLocal;
|
||||
float u = (1.0f + (localPos | targetSurface->projLocalToU)) / (targetSurface->texWidth + 2);
|
||||
float v = (1.0f + (localPos | targetSurface->projLocalToV)) / (targetSurface->texHeight + 2);
|
||||
return FVector2(u, v);
|
||||
}
|
||||
|
||||
void VkLightmap::CreateShaders()
|
||||
{
|
||||
std::string prefix = "#version 460\r\n";
|
||||
std::string traceprefix = "#version 460\r\n";
|
||||
if (useRayQuery)
|
||||
{
|
||||
traceprefix += "#extension GL_EXT_ray_query : require\r\n";
|
||||
traceprefix += "#define USE_RAYQUERY\r\n";
|
||||
}
|
||||
|
||||
vertShader = ShaderBuilder()
|
||||
.Type(ShaderType::Vertex)
|
||||
.AddSource("VersionBlock", prefix)
|
||||
.AddSource("vert.glsl", LoadPrivateShaderLump("shaders/lightmap/vert.glsl").GetChars())
|
||||
.DebugName("VkLightmap.VertShader")
|
||||
.Create("VkLightmap.VertShader", fb->GetDevice());
|
||||
|
||||
fragShader = ShaderBuilder()
|
||||
.Type(ShaderType::Fragment)
|
||||
.AddSource("VersionBlock", traceprefix)
|
||||
.AddSource("frag.glsl", LoadPrivateShaderLump("shaders/lightmap/frag.glsl").GetChars())
|
||||
.DebugName("VkLightmap.FragShader")
|
||||
.Create("VkLightmap.FragShader", fb->GetDevice());
|
||||
|
||||
fragResolveShader = ShaderBuilder()
|
||||
.Type(ShaderType::Fragment)
|
||||
.AddSource("VersionBlock", prefix)
|
||||
.AddSource("frag_resolve.glsl", LoadPrivateShaderLump("shaders/lightmap/frag_resolve.glsl").GetChars())
|
||||
.DebugName("VkLightmap.FragResolveShader")
|
||||
.Create("VkLightmap.FragResolveShader", fb->GetDevice());
|
||||
}
|
||||
|
||||
FString VkLightmap::LoadPrivateShaderLump(const char* lumpname)
|
||||
{
|
||||
int lump = fileSystem.CheckNumForFullName(lumpname, 0);
|
||||
if (lump == -1) I_Error("Unable to load '%s'", lumpname);
|
||||
auto data = fileSystem.ReadFile(lump);
|
||||
return data.GetString();
|
||||
}
|
||||
|
||||
void VkLightmap::CreateRaytracePipeline()
|
||||
{
|
||||
raytrace.descriptorSetLayout0 = DescriptorSetLayoutBuilder()
|
||||
.AddBinding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT)
|
||||
.AddBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT)
|
||||
.AddBinding(2, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT)
|
||||
.AddBinding(3, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT)
|
||||
.AddBinding(4, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT)
|
||||
.DebugName("raytrace.descriptorSetLayout0")
|
||||
.Create(fb->GetDevice());
|
||||
|
||||
if (useRayQuery)
|
||||
{
|
||||
raytrace.descriptorSetLayout1 = DescriptorSetLayoutBuilder()
|
||||
.AddBinding(0, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_FRAGMENT_BIT)
|
||||
.DebugName("raytrace.descriptorSetLayout1")
|
||||
.Create(fb->GetDevice());
|
||||
}
|
||||
else
|
||||
{
|
||||
raytrace.descriptorSetLayout1 = DescriptorSetLayoutBuilder()
|
||||
.AddBinding(0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT)
|
||||
.AddBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT)
|
||||
.AddBinding(2, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT)
|
||||
.DebugName("raytrace.descriptorSetLayout1")
|
||||
.Create(fb->GetDevice());
|
||||
}
|
||||
|
||||
raytrace.pipelineLayout = PipelineLayoutBuilder()
|
||||
.AddSetLayout(raytrace.descriptorSetLayout0.get())
|
||||
.AddSetLayout(raytrace.descriptorSetLayout1.get())
|
||||
.AddPushConstantRange(VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstants))
|
||||
.DebugName("raytrace.pipelineLayout")
|
||||
.Create(fb->GetDevice());
|
||||
|
||||
raytrace.renderPass = RenderPassBuilder()
|
||||
.AddAttachment(
|
||||
VK_FORMAT_R16G16B16A16_SFLOAT,
|
||||
VK_SAMPLE_COUNT_4_BIT,
|
||||
VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
||||
VK_ATTACHMENT_STORE_OP_STORE,
|
||||
VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL)
|
||||
.AddSubpass()
|
||||
.AddSubpassColorAttachmentRef(0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL)
|
||||
.AddExternalSubpassDependency(
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
||||
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT)
|
||||
.DebugName("raytrace.renderpass")
|
||||
.Create(fb->GetDevice());
|
||||
|
||||
raytrace.pipeline = GraphicsPipelineBuilder()
|
||||
.Layout(raytrace.pipelineLayout.get())
|
||||
.RenderPass(raytrace.renderPass.get())
|
||||
.AddVertexShader(vertShader.get())
|
||||
.AddFragmentShader(fragShader.get())
|
||||
.AddVertexBufferBinding(0, sizeof(SceneVertex))
|
||||
.AddVertexAttribute(0, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(SceneVertex, Position))
|
||||
.Topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN)
|
||||
.AddDynamicState(VK_DYNAMIC_STATE_VIEWPORT)
|
||||
.RasterizationSamples(VK_SAMPLE_COUNT_4_BIT)
|
||||
.Viewport(0.0f, 0.0f, 0.0f, 0.0f)
|
||||
.Scissor(0, 0, 4096, 4096)
|
||||
.DebugName("raytrace.pipeline")
|
||||
.Create(fb->GetDevice());
|
||||
|
||||
raytrace.descriptorPool0 = DescriptorPoolBuilder()
|
||||
.AddPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1)
|
||||
.AddPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 4)
|
||||
.MaxSets(1)
|
||||
.DebugName("raytrace.descriptorPool0")
|
||||
.Create(fb->GetDevice());
|
||||
|
||||
if (useRayQuery)
|
||||
{
|
||||
raytrace.descriptorPool1 = DescriptorPoolBuilder()
|
||||
.AddPoolSize(VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1)
|
||||
.MaxSets(1)
|
||||
.DebugName("raytrace.descriptorPool1")
|
||||
.Create(fb->GetDevice());
|
||||
}
|
||||
else
|
||||
{
|
||||
raytrace.descriptorPool1 = DescriptorPoolBuilder()
|
||||
.AddPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 3)
|
||||
.MaxSets(1)
|
||||
.DebugName("raytrace.descriptorPool1")
|
||||
.Create(fb->GetDevice());
|
||||
}
|
||||
|
||||
raytrace.descriptorSet0 = raytrace.descriptorPool0->allocate(raytrace.descriptorSetLayout0.get());
|
||||
raytrace.descriptorSet0->SetDebugName("raytrace.descriptorSet1");
|
||||
|
||||
raytrace.descriptorSet1 = raytrace.descriptorPool1->allocate(raytrace.descriptorSetLayout1.get());
|
||||
raytrace.descriptorSet1->SetDebugName("raytrace.descriptorSet1");
|
||||
}
|
||||
|
||||
void VkLightmap::UpdateAccelStructDescriptors()
|
||||
{
|
||||
// To do: fetch this from VkDescriptorSetManager - perhaps manage it all over there?
|
||||
|
||||
#if 0
|
||||
if (useRayQuery)
|
||||
{
|
||||
WriteDescriptors()
|
||||
.AddAccelerationStructure(raytrace.descriptorSet1.get(), 0, tlAccelStruct.get())
|
||||
.Execute(fb->GetDevice());
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteDescriptors()
|
||||
.AddBuffer(raytrace.descriptorSet1.get(), 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, nodesBuffer.get())
|
||||
.AddBuffer(raytrace.descriptorSet1.get(), 1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, vertexBuffer.get())
|
||||
.AddBuffer(raytrace.descriptorSet1.get(), 2, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, indexBuffer.get())
|
||||
.Execute(fb->GetDevice());
|
||||
}
|
||||
|
||||
WriteDescriptors()
|
||||
.AddBuffer(raytrace.descriptorSet0.get(), 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, uniformBuffer.get(), 0, sizeof(Uniforms))
|
||||
.AddBuffer(raytrace.descriptorSet0.get(), 1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, surfaceIndexBuffer.get())
|
||||
.AddBuffer(raytrace.descriptorSet0.get(), 2, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, surfaceBuffer.get())
|
||||
.AddBuffer(raytrace.descriptorSet0.get(), 3, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, sceneLightBuffer.get())
|
||||
.AddBuffer(raytrace.descriptorSet0.get(), 4, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, portalBuffer.get())
|
||||
.Execute(fb->GetDevice());
|
||||
#endif
|
||||
}
|
||||
|
||||
void VkLightmap::CreateResolvePipeline()
|
||||
{
|
||||
resolve.descriptorSetLayout = DescriptorSetLayoutBuilder()
|
||||
.AddBinding(0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT)
|
||||
.DebugName("resolve.descriptorSetLayout")
|
||||
.Create(fb->GetDevice());
|
||||
|
||||
resolve.pipelineLayout = PipelineLayoutBuilder()
|
||||
.AddSetLayout(resolve.descriptorSetLayout.get())
|
||||
.AddPushConstantRange(VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(PushConstants))
|
||||
.DebugName("resolve.pipelineLayout")
|
||||
.Create(fb->GetDevice());
|
||||
|
||||
resolve.renderPass = RenderPassBuilder()
|
||||
.AddAttachment(
|
||||
VK_FORMAT_R16G16B16A16_SFLOAT,
|
||||
VK_SAMPLE_COUNT_1_BIT,
|
||||
VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
||||
VK_ATTACHMENT_STORE_OP_STORE,
|
||||
VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL)
|
||||
.AddSubpass()
|
||||
.AddSubpassColorAttachmentRef(0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL)
|
||||
.AddExternalSubpassDependency(
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
||||
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT)
|
||||
.DebugName("resolve.renderpass")
|
||||
.Create(fb->GetDevice());
|
||||
|
||||
resolve.pipeline = GraphicsPipelineBuilder()
|
||||
.Layout(resolve.pipelineLayout.get())
|
||||
.RenderPass(resolve.renderPass.get())
|
||||
.AddVertexShader(vertShader.get())
|
||||
.AddFragmentShader(fragResolveShader.get())
|
||||
.AddVertexBufferBinding(0, sizeof(SceneVertex))
|
||||
.AddVertexAttribute(0, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(SceneVertex, Position))
|
||||
.Topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN)
|
||||
.AddDynamicState(VK_DYNAMIC_STATE_VIEWPORT)
|
||||
.Viewport(0.0f, 0.0f, 0.0f, 0.0f)
|
||||
.Scissor(0, 0, 4096, 4096)
|
||||
.DebugName("resolve.pipeline")
|
||||
.Create(fb->GetDevice());
|
||||
|
||||
resolve.descriptorPool = DescriptorPoolBuilder()
|
||||
.AddPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 256)
|
||||
.MaxSets(256)
|
||||
.DebugName("resolve.descriptorPool")
|
||||
.Create(fb->GetDevice());
|
||||
|
||||
resolve.sampler = SamplerBuilder()
|
||||
.DebugName("resolve.Sampler")
|
||||
.Create(fb->GetDevice());
|
||||
}
|
||||
|
||||
LightmapImage VkLightmap::CreateImage(int width, int height)
|
||||
{
|
||||
LightmapImage img;
|
||||
|
||||
img.raytrace.Image = ImageBuilder()
|
||||
.Usage(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT)
|
||||
.Format(VK_FORMAT_R16G16B16A16_SFLOAT)
|
||||
.Size(width, height)
|
||||
.Samples(VK_SAMPLE_COUNT_4_BIT)
|
||||
.DebugName("LightmapImage.raytrace.Image")
|
||||
.Create(fb->GetDevice());
|
||||
|
||||
img.raytrace.View = ImageViewBuilder()
|
||||
.Image(img.raytrace.Image.get(), VK_FORMAT_R16G16B16A16_SFLOAT)
|
||||
.DebugName("LightmapImage.raytrace.View")
|
||||
.Create(fb->GetDevice());
|
||||
|
||||
img.raytrace.Framebuffer = FramebufferBuilder()
|
||||
.RenderPass(raytrace.renderPass.get())
|
||||
.Size(width, height)
|
||||
.AddAttachment(img.raytrace.View.get())
|
||||
.DebugName("LightmapImage.raytrace.Framebuffer")
|
||||
.Create(fb->GetDevice());
|
||||
|
||||
img.resolve.Image = ImageBuilder()
|
||||
.Usage(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT)
|
||||
.Format(VK_FORMAT_R16G16B16A16_SFLOAT)
|
||||
.Size(width, height)
|
||||
.DebugName("LightmapImage.resolve.Image")
|
||||
.Create(fb->GetDevice());
|
||||
|
||||
img.resolve.View = ImageViewBuilder()
|
||||
.Image(img.resolve.Image.get(), VK_FORMAT_R16G16B16A16_SFLOAT)
|
||||
.DebugName("LightmapImage.resolve.View")
|
||||
.Create(fb->GetDevice());
|
||||
|
||||
img.resolve.Framebuffer = FramebufferBuilder()
|
||||
.RenderPass(resolve.renderPass.get())
|
||||
.Size(width, height)
|
||||
.AddAttachment(img.resolve.View.get())
|
||||
.DebugName("LightmapImage.resolve.Framebuffer")
|
||||
.Create(fb->GetDevice());
|
||||
|
||||
img.Transfer = BufferBuilder()
|
||||
.Size(width * height * sizeof(FVector4))
|
||||
.Usage(VK_IMAGE_USAGE_TRANSFER_DST_BIT, VMA_MEMORY_USAGE_CPU_ONLY)
|
||||
.DebugName("LightmapImage.Transfer")
|
||||
.Create(fb->GetDevice());
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
void VkLightmap::CreateUniformBuffer()
|
||||
{
|
||||
VkDeviceSize align = fb->GetDevice()->PhysicalDevice.Properties.Properties.limits.minUniformBufferOffsetAlignment;
|
||||
uniformStructStride = (sizeof(Uniforms) + align - 1) / align * align;
|
||||
|
||||
uniformBuffer = BufferBuilder()
|
||||
.Usage(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT)
|
||||
.Size(uniformStructs * uniformStructStride)
|
||||
.DebugName("uniformBuffer")
|
||||
.Create(fb->GetDevice());
|
||||
|
||||
uniformTransferBuffer = BufferBuilder()
|
||||
.Usage(VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU)
|
||||
.Size(uniformStructs * uniformStructStride)
|
||||
.DebugName("uniformTransferBuffer")
|
||||
.Create(fb->GetDevice());
|
||||
}
|
||||
|
||||
void VkLightmap::CreateSceneVertexBuffer()
|
||||
{
|
||||
size_t size = sizeof(SceneVertex) * SceneVertexBufferSize;
|
||||
|
||||
sceneVertexBuffer = BufferBuilder()
|
||||
.Usage(
|
||||
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_UNKNOWN, VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT)
|
||||
.MemoryType(
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)
|
||||
.Size(size)
|
||||
.DebugName("SceneVertexBuffer")
|
||||
.Create(fb->GetDevice());
|
||||
|
||||
sceneVertices = (SceneVertex*)sceneVertexBuffer->Map(0, size);
|
||||
sceneVertexPos = 0;
|
||||
}
|
||||
|
||||
void VkLightmap::CreateSceneLightBuffer()
|
||||
{
|
||||
size_t size = sizeof(LightInfo) * SceneLightBufferSize;
|
||||
|
||||
sceneLightBuffer = BufferBuilder()
|
||||
.Usage(
|
||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_UNKNOWN, VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT)
|
||||
.MemoryType(
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)
|
||||
.Size(size)
|
||||
.DebugName("SceneLightBuffer")
|
||||
.Create(fb->GetDevice());
|
||||
|
||||
sceneLights = (LightInfo*)sceneLightBuffer->Map(0, size);
|
||||
sceneLightPos = 0;
|
||||
}
|
166
src/common/rendering/vulkan/accelstructs/vk_lightmap.h
Normal file
166
src/common/rendering/vulkan/accelstructs/vk_lightmap.h
Normal file
|
@ -0,0 +1,166 @@
|
|||
#pragma once
|
||||
|
||||
#include "common/rendering/hwrenderer/data/hw_levelmesh.h"
|
||||
#include "zvulkan/vulkanobjects.h"
|
||||
#include <dp_rect_pack.h>
|
||||
|
||||
typedef dp::rect_pack::RectPacker<int> RectPacker;
|
||||
|
||||
class VulkanRenderDevice;
|
||||
class FString;
|
||||
|
||||
struct Uniforms
|
||||
{
|
||||
FVector3 SunDir;
|
||||
float Padding1;
|
||||
FVector3 SunColor;
|
||||
float SunIntensity;
|
||||
};
|
||||
|
||||
struct PushConstants
|
||||
{
|
||||
uint32_t LightStart;
|
||||
uint32_t LightEnd;
|
||||
int32_t SurfaceIndex;
|
||||
int32_t PushPadding1;
|
||||
FVector3 LightmapOrigin;
|
||||
float PushPadding2;
|
||||
FVector3 LightmapStepX;
|
||||
float PushPadding3;
|
||||
FVector3 LightmapStepY;
|
||||
float PushPadding4;
|
||||
};
|
||||
|
||||
struct LightmapImage
|
||||
{
|
||||
struct
|
||||
{
|
||||
std::unique_ptr<VulkanImage> Image;
|
||||
std::unique_ptr<VulkanImageView> View;
|
||||
std::unique_ptr<VulkanFramebuffer> Framebuffer;
|
||||
} raytrace;
|
||||
|
||||
struct
|
||||
{
|
||||
std::unique_ptr<VulkanImage> Image;
|
||||
std::unique_ptr<VulkanImageView> View;
|
||||
std::unique_ptr<VulkanFramebuffer> Framebuffer;
|
||||
} resolve;
|
||||
|
||||
std::unique_ptr<VulkanBuffer> Transfer;
|
||||
};
|
||||
|
||||
struct SceneVertex
|
||||
{
|
||||
FVector2 Position;
|
||||
};
|
||||
|
||||
struct LightInfo
|
||||
{
|
||||
FVector3 Origin;
|
||||
float Padding0;
|
||||
FVector3 RelativeOrigin;
|
||||
float Padding1;
|
||||
float Radius;
|
||||
float Intensity;
|
||||
float InnerAngleCos;
|
||||
float OuterAngleCos;
|
||||
FVector3 SpotDir;
|
||||
float Padding2;
|
||||
FVector3 Color;
|
||||
float Padding3;
|
||||
};
|
||||
|
||||
static_assert(sizeof(LightInfo) == sizeof(float) * 20);
|
||||
|
||||
class VkLightmap
|
||||
{
|
||||
public:
|
||||
VkLightmap(VulkanRenderDevice* fb);
|
||||
~VkLightmap();
|
||||
|
||||
void Raytrace(hwrenderer::LevelMesh* level);
|
||||
|
||||
private:
|
||||
void UpdateAccelStructDescriptors();
|
||||
|
||||
void BeginCommands();
|
||||
void FinishCommands();
|
||||
|
||||
void UploadUniforms();
|
||||
void CreateAtlasImages();
|
||||
void RenderAtlasImage(size_t pageIndex);
|
||||
void ResolveAtlasImage(size_t pageIndex);
|
||||
void DownloadAtlasImage(size_t pageIndex);
|
||||
|
||||
LightmapImage CreateImage(int width, int height);
|
||||
|
||||
void CreateShaders();
|
||||
void CreateRaytracePipeline();
|
||||
void CreateResolvePipeline();
|
||||
void CreateUniformBuffer();
|
||||
void CreateSceneVertexBuffer();
|
||||
void CreateSceneLightBuffer();
|
||||
|
||||
static FVector2 ToUV(const FVector3& vert, const hwrenderer::Surface* targetSurface);
|
||||
|
||||
static FString LoadPrivateShaderLump(const char* lumpname);
|
||||
|
||||
VulkanRenderDevice* fb = nullptr;
|
||||
hwrenderer::LevelMesh* mesh = nullptr;
|
||||
|
||||
bool useRayQuery = true;
|
||||
|
||||
uint8_t* mappedUniforms = nullptr;
|
||||
int uniformsIndex = 0;
|
||||
int uniformStructs = 256;
|
||||
VkDeviceSize uniformStructStride = sizeof(Uniforms);
|
||||
|
||||
static const int SceneVertexBufferSize = 1 * 1024 * 1024;
|
||||
std::unique_ptr<VulkanBuffer> sceneVertexBuffer;
|
||||
SceneVertex* sceneVertices = nullptr;
|
||||
int sceneVertexPos = 0;
|
||||
|
||||
static const int SceneLightBufferSize = 2 * 1024 * 1024;
|
||||
std::unique_ptr<VulkanBuffer> sceneLightBuffer;
|
||||
LightInfo* sceneLights = nullptr;
|
||||
int sceneLightPos = 0;
|
||||
|
||||
std::unique_ptr<VulkanShader> vertShader;
|
||||
std::unique_ptr<VulkanShader> fragShader;
|
||||
std::unique_ptr<VulkanShader> fragResolveShader;
|
||||
|
||||
struct
|
||||
{
|
||||
std::unique_ptr<VulkanDescriptorSetLayout> descriptorSetLayout0;
|
||||
std::unique_ptr<VulkanDescriptorSetLayout> descriptorSetLayout1;
|
||||
std::unique_ptr<VulkanPipelineLayout> pipelineLayout;
|
||||
std::unique_ptr<VulkanPipeline> pipeline;
|
||||
std::unique_ptr<VulkanRenderPass> renderPass;
|
||||
std::unique_ptr<VulkanDescriptorPool> descriptorPool0;
|
||||
std::unique_ptr<VulkanDescriptorPool> descriptorPool1;
|
||||
std::unique_ptr<VulkanDescriptorSet> descriptorSet0;
|
||||
std::unique_ptr<VulkanDescriptorSet> descriptorSet1;
|
||||
} raytrace;
|
||||
|
||||
struct
|
||||
{
|
||||
std::unique_ptr<VulkanDescriptorSetLayout> descriptorSetLayout;
|
||||
std::unique_ptr<VulkanPipelineLayout> pipelineLayout;
|
||||
std::unique_ptr<VulkanPipeline> pipeline;
|
||||
std::unique_ptr<VulkanRenderPass> renderPass;
|
||||
std::unique_ptr<VulkanDescriptorPool> descriptorPool;
|
||||
std::vector<std::unique_ptr<VulkanDescriptorSet>> descriptorSets;
|
||||
std::unique_ptr<VulkanSampler> sampler;
|
||||
} resolve;
|
||||
|
||||
std::unique_ptr<VulkanBuffer> uniformBuffer;
|
||||
std::unique_ptr<VulkanBuffer> uniformTransferBuffer;
|
||||
|
||||
std::unique_ptr<VulkanFence> submitFence;
|
||||
std::unique_ptr<VulkanCommandPool> cmdpool;
|
||||
std::unique_ptr<VulkanCommandBuffer> cmdbuffer;
|
||||
|
||||
std::vector<LightmapImage> atlasImages;
|
||||
static const int atlasImageSize = 2048;
|
||||
};
|
563
wadsrc/static/shaders/lightmap/frag.glsl
Normal file
563
wadsrc/static/shaders/lightmap/frag.glsl
Normal file
|
@ -0,0 +1,563 @@
|
|||
|
||||
#if defined(USE_RAYQUERY)
|
||||
layout(set = 1, binding = 0) uniform accelerationStructureEXT acc;
|
||||
#else
|
||||
struct CollisionNode
|
||||
{
|
||||
vec3 center;
|
||||
float padding1;
|
||||
vec3 extents;
|
||||
float padding2;
|
||||
int left;
|
||||
int right;
|
||||
int element_index;
|
||||
int padding3;
|
||||
};
|
||||
layout(std430, set = 1, binding = 0) buffer NodeBuffer
|
||||
{
|
||||
int nodesRoot;
|
||||
int nodebufferPadding1;
|
||||
int nodebufferPadding2;
|
||||
int nodebufferPadding3;
|
||||
CollisionNode nodes[];
|
||||
};
|
||||
layout(std430, set = 1, binding = 1) buffer VertexBuffer { vec4 vertices[]; };
|
||||
layout(std430, set = 1, binding = 2) buffer ElementBuffer { int elements[]; };
|
||||
#endif
|
||||
|
||||
layout(set = 0, binding = 0) uniform Uniforms
|
||||
{
|
||||
vec3 SunDir;
|
||||
float Padding1;
|
||||
vec3 SunColor;
|
||||
float SunIntensity;
|
||||
};
|
||||
|
||||
struct SurfaceInfo
|
||||
{
|
||||
vec3 Normal;
|
||||
float Sky;
|
||||
float SamplingDistance;
|
||||
uint PortalIndex;
|
||||
float Padding1, Padding2;
|
||||
};
|
||||
|
||||
struct PortalInfo
|
||||
{
|
||||
mat4 Transformation;
|
||||
};
|
||||
|
||||
struct LightInfo
|
||||
{
|
||||
vec3 Origin;
|
||||
float Padding0;
|
||||
vec3 RelativeOrigin;
|
||||
float Padding1;
|
||||
float Radius;
|
||||
float Intensity;
|
||||
float InnerAngleCos;
|
||||
float OuterAngleCos;
|
||||
vec3 SpotDir;
|
||||
float Padding2;
|
||||
vec3 Color;
|
||||
float Padding3;
|
||||
};
|
||||
|
||||
layout(set = 0, binding = 1) buffer SurfaceIndexBuffer { uint surfaceIndices[]; };
|
||||
layout(set = 0, binding = 2) buffer SurfaceBuffer { SurfaceInfo surfaces[]; };
|
||||
layout(set = 0, binding = 3) buffer LightBuffer { LightInfo lights[]; };
|
||||
layout(set = 0, binding = 4) buffer PortalBuffer { PortalInfo portals[]; };
|
||||
|
||||
layout(push_constant) uniform PushConstants
|
||||
{
|
||||
uint LightStart;
|
||||
uint LightEnd;
|
||||
int SurfaceIndex;
|
||||
int PushPadding1;
|
||||
vec3 LightmapOrigin;
|
||||
float PushPadding2;
|
||||
vec3 LightmapStepX;
|
||||
float PushPadding3;
|
||||
vec3 LightmapStepY;
|
||||
float PushPadding4;
|
||||
};
|
||||
|
||||
layout(location = 0) centroid in vec3 worldpos;
|
||||
layout(location = 0) out vec4 fragcolor;
|
||||
|
||||
vec3 TraceSunLight(vec3 origin);
|
||||
vec3 TraceLight(vec3 origin, vec3 normal, LightInfo light);
|
||||
float TraceAmbientOcclusion(vec3 origin, vec3 normal);
|
||||
vec2 Hammersley(uint i, uint N);
|
||||
float RadicalInverse_VdC(uint bits);
|
||||
|
||||
bool TraceAnyHit(vec3 origin, float tmin, vec3 dir, float tmax);
|
||||
bool TracePoint(vec3 origin, vec3 target, float tmin, vec3 dir, float tmax);
|
||||
int TraceFirstHitTriangle(vec3 origin, float tmin, vec3 dir, float tmax);
|
||||
int TraceFirstHitTriangleT(vec3 origin, float tmin, vec3 dir, float tmax, out float t);
|
||||
|
||||
void main()
|
||||
{
|
||||
vec3 normal = surfaces[SurfaceIndex].Normal;
|
||||
vec3 origin = worldpos + normal * 0.1;
|
||||
|
||||
vec3 incoming = TraceSunLight(origin);
|
||||
|
||||
for (uint j = LightStart; j < LightEnd; j++)
|
||||
{
|
||||
incoming += TraceLight(origin, normal, lights[j]);
|
||||
}
|
||||
|
||||
#if defined(USE_RAYQUERY) // The non-rtx version of TraceFirstHitTriangle is too slow to do AO without the shader getting killed ;(
|
||||
incoming.rgb *= TraceAmbientOcclusion(origin, normal);
|
||||
#endif
|
||||
|
||||
fragcolor = vec4(incoming, 1.0);
|
||||
}
|
||||
|
||||
vec3 TraceLight(vec3 origin, vec3 normal, LightInfo light)
|
||||
{
|
||||
const float minDistance = 0.01;
|
||||
vec3 incoming = vec3(0.0);
|
||||
float dist = distance(light.RelativeOrigin, origin);
|
||||
if (dist > minDistance && dist < light.Radius)
|
||||
{
|
||||
vec3 dir = normalize(light.RelativeOrigin - origin);
|
||||
|
||||
float distAttenuation = max(1.0 - (dist / light.Radius), 0.0);
|
||||
float angleAttenuation = 1.0f;
|
||||
if (SurfaceIndex >= 0)
|
||||
{
|
||||
angleAttenuation = max(dot(normal, dir), 0.0);
|
||||
}
|
||||
float spotAttenuation = 1.0;
|
||||
if (light.OuterAngleCos > -1.0)
|
||||
{
|
||||
float cosDir = dot(dir, light.SpotDir);
|
||||
spotAttenuation = smoothstep(light.OuterAngleCos, light.InnerAngleCos, cosDir);
|
||||
spotAttenuation = max(spotAttenuation, 0.0);
|
||||
}
|
||||
|
||||
float attenuation = distAttenuation * angleAttenuation * spotAttenuation;
|
||||
if (attenuation > 0.0)
|
||||
{
|
||||
if(TracePoint(origin, light.Origin, minDistance, dir, dist))
|
||||
{
|
||||
incoming.rgb += light.Color * (attenuation * light.Intensity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return incoming;
|
||||
}
|
||||
|
||||
vec3 TraceSunLight(vec3 origin)
|
||||
{
|
||||
const float minDistance = 0.01;
|
||||
vec3 incoming = vec3(0.0);
|
||||
const float dist = 32768.0;
|
||||
|
||||
int primitiveID = TraceFirstHitTriangle(origin, minDistance, SunDir, dist);
|
||||
if (primitiveID != -1)
|
||||
{
|
||||
SurfaceInfo surface = surfaces[surfaceIndices[primitiveID]];
|
||||
incoming.rgb += SunColor * SunIntensity * surface.Sky;
|
||||
}
|
||||
return incoming;
|
||||
}
|
||||
|
||||
float TraceAmbientOcclusion(vec3 origin, vec3 normal)
|
||||
{
|
||||
const float minDistance = 0.05;
|
||||
const float aoDistance = 100;
|
||||
const int SampleCount = 2048;
|
||||
|
||||
vec3 N = normal;
|
||||
vec3 up = abs(N.x) < abs(N.y) ? vec3(1.0, 0.0, 0.0) : vec3(0.0, 1.0, 0.0);
|
||||
vec3 tangent = normalize(cross(up, N));
|
||||
vec3 bitangent = cross(N, tangent);
|
||||
|
||||
float ambience = 0.0f;
|
||||
for (uint i = 0; i < SampleCount; i++)
|
||||
{
|
||||
vec2 Xi = Hammersley(i, SampleCount);
|
||||
vec3 H = normalize(vec3(Xi.x * 2.0f - 1.0f, Xi.y * 2.0f - 1.0f, 1.5 - length(Xi)));
|
||||
vec3 L = H.x * tangent + H.y * bitangent + H.z * N;
|
||||
|
||||
float hitDistance;
|
||||
int primitiveID = TraceFirstHitTriangleT(origin, minDistance, L, aoDistance, hitDistance);
|
||||
if (primitiveID != -1)
|
||||
{
|
||||
SurfaceInfo surface = surfaces[surfaceIndices[primitiveID]];
|
||||
if (surface.Sky == 0.0)
|
||||
{
|
||||
ambience += clamp(hitDistance / aoDistance, 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ambience += 1.0;
|
||||
}
|
||||
}
|
||||
return ambience / float(SampleCount);
|
||||
}
|
||||
|
||||
vec2 Hammersley(uint i, uint N)
|
||||
{
|
||||
return vec2(float(i) / float(N), RadicalInverse_VdC(i));
|
||||
}
|
||||
|
||||
float RadicalInverse_VdC(uint bits)
|
||||
{
|
||||
bits = (bits << 16u) | (bits >> 16u);
|
||||
bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
|
||||
bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
|
||||
bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
|
||||
bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
|
||||
return float(bits) * 2.3283064365386963e-10f; // / 0x100000000
|
||||
}
|
||||
|
||||
#if defined(USE_RAYQUERY)
|
||||
|
||||
int TraceFirstHitTriangleNoPortal(vec3 origin, float tmin, vec3 dir, float tmax, out float t)
|
||||
{
|
||||
rayQueryEXT rayQuery;
|
||||
rayQueryInitializeEXT(rayQuery, acc, gl_RayFlagsTerminateOnFirstHitEXT, 0xFF, origin, tmin, dir, tmax);
|
||||
|
||||
while(rayQueryProceedEXT(rayQuery))
|
||||
{
|
||||
if (rayQueryGetIntersectionTypeEXT(rayQuery, false) == gl_RayQueryCommittedIntersectionTriangleEXT)
|
||||
{
|
||||
rayQueryConfirmIntersectionEXT(rayQuery);
|
||||
}
|
||||
}
|
||||
|
||||
if (rayQueryGetIntersectionTypeEXT(rayQuery, true) == gl_RayQueryCommittedIntersectionTriangleEXT)
|
||||
{
|
||||
t = rayQueryGetIntersectionTEXT(rayQuery, true);
|
||||
return rayQueryGetIntersectionPrimitiveIndexEXT(rayQuery, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
t = tmax;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
bool TraceAnyHit(vec3 origin, float tmin, vec3 dir, float tmax)
|
||||
{
|
||||
rayQueryEXT rayQuery;
|
||||
rayQueryInitializeEXT(rayQuery, acc, gl_RayFlagsTerminateOnFirstHitEXT, 0xFF, origin, tmin, dir, tmax);
|
||||
while(rayQueryProceedEXT(rayQuery)) { }
|
||||
return rayQueryGetIntersectionTypeEXT(rayQuery, true) != gl_RayQueryCommittedIntersectionNoneEXT;
|
||||
}
|
||||
*/
|
||||
|
||||
#else
|
||||
|
||||
struct RayBBox
|
||||
{
|
||||
vec3 start, end;
|
||||
vec3 c, w, v;
|
||||
};
|
||||
|
||||
RayBBox create_ray(vec3 ray_start, vec3 ray_end)
|
||||
{
|
||||
RayBBox ray;
|
||||
ray.start = ray_start;
|
||||
ray.end = ray_end;
|
||||
ray.c = (ray_start + ray_end) * 0.5;
|
||||
ray.w = ray_end - ray.c;
|
||||
ray.v = abs(ray.w);
|
||||
return ray;
|
||||
}
|
||||
|
||||
bool overlap_bv_ray(RayBBox ray, int a)
|
||||
{
|
||||
vec3 v = ray.v;
|
||||
vec3 w = ray.w;
|
||||
vec3 h = nodes[a].extents;
|
||||
vec3 c = ray.c - nodes[a].center;
|
||||
|
||||
if (abs(c.x) > v.x + h.x ||
|
||||
abs(c.y) > v.y + h.y ||
|
||||
abs(c.z) > v.z + h.z)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (abs(c.y * w.z - c.z * w.y) > h.y * v.z + h.z * v.y ||
|
||||
abs(c.x * w.z - c.z * w.x) > h.x * v.z + h.z * v.x ||
|
||||
abs(c.x * w.y - c.y * w.x) > h.x * v.y + h.y * v.x)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#define FLT_EPSILON 1.192092896e-07F // smallest such that 1.0+FLT_EPSILON != 1.0
|
||||
|
||||
float intersect_triangle_ray(RayBBox ray, int a, out float barycentricB, out float barycentricC)
|
||||
{
|
||||
int start_element = nodes[a].element_index;
|
||||
|
||||
vec3 p[3];
|
||||
p[0] = vertices[elements[start_element]].xyz;
|
||||
p[1] = vertices[elements[start_element + 1]].xyz;
|
||||
p[2] = vertices[elements[start_element + 2]].xyz;
|
||||
|
||||
// Moeller-Trumbore ray-triangle intersection algorithm:
|
||||
|
||||
vec3 D = ray.end - ray.start;
|
||||
|
||||
// Find vectors for two edges sharing p[0]
|
||||
vec3 e1 = p[1] - p[0];
|
||||
vec3 e2 = p[2] - p[0];
|
||||
|
||||
// Begin calculating determinant - also used to calculate u parameter
|
||||
vec3 P = cross(D, e2);
|
||||
float det = dot(e1, P);
|
||||
|
||||
// Backface check
|
||||
//if (det < 0.0f)
|
||||
// return 1.0f;
|
||||
|
||||
// If determinant is near zero, ray lies in plane of triangle
|
||||
if (det > -FLT_EPSILON && det < FLT_EPSILON)
|
||||
return 1.0f;
|
||||
|
||||
float inv_det = 1.0f / det;
|
||||
|
||||
// Calculate distance from p[0] to ray origin
|
||||
vec3 T = ray.start - p[0];
|
||||
|
||||
// Calculate u parameter and test bound
|
||||
float u = dot(T, P) * inv_det;
|
||||
|
||||
// Check if the intersection lies outside of the triangle
|
||||
if (u < 0.f || u > 1.f)
|
||||
return 1.0f;
|
||||
|
||||
// Prepare to test v parameter
|
||||
vec3 Q = cross(T, e1);
|
||||
|
||||
// Calculate V parameter and test bound
|
||||
float v = dot(D, Q) * inv_det;
|
||||
|
||||
// The intersection lies outside of the triangle
|
||||
if (v < 0.f || u + v > 1.f)
|
||||
return 1.0f;
|
||||
|
||||
float t = dot(e2, Q) * inv_det;
|
||||
if (t <= FLT_EPSILON)
|
||||
return 1.0f;
|
||||
|
||||
// Return hit location on triangle in barycentric coordinates
|
||||
barycentricB = u;
|
||||
barycentricC = v;
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
bool is_leaf(int node_index)
|
||||
{
|
||||
return nodes[node_index].element_index != -1;
|
||||
}
|
||||
|
||||
/*
|
||||
bool TraceAnyHit(vec3 origin, float tmin, vec3 dir, float tmax)
|
||||
{
|
||||
if (tmax <= 0.0f)
|
||||
return false;
|
||||
|
||||
RayBBox ray = create_ray(origin, origin + dir * tmax);
|
||||
tmin /= tmax;
|
||||
|
||||
int stack[64];
|
||||
int stackIndex = 0;
|
||||
stack[stackIndex++] = nodesRoot;
|
||||
do
|
||||
{
|
||||
int a = stack[--stackIndex];
|
||||
if (overlap_bv_ray(ray, a))
|
||||
{
|
||||
if (is_leaf(a))
|
||||
{
|
||||
float baryB, baryC;
|
||||
float t = intersect_triangle_ray(ray, a, baryB, baryC);
|
||||
if (t >= tmin && t < 1.0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stack[stackIndex++] = nodes[a].right;
|
||||
stack[stackIndex++] = nodes[a].left;
|
||||
}
|
||||
}
|
||||
} while (stackIndex > 0);
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
|
||||
struct TraceHit
|
||||
{
|
||||
float fraction;
|
||||
int triangle;
|
||||
float b;
|
||||
float c;
|
||||
};
|
||||
|
||||
TraceHit find_first_hit(RayBBox ray)
|
||||
{
|
||||
TraceHit hit;
|
||||
hit.fraction = 1.0;
|
||||
hit.triangle = -1;
|
||||
hit.b = 0.0;
|
||||
hit.c = 0.0;
|
||||
|
||||
int stack[64];
|
||||
int stackIndex = 0;
|
||||
stack[stackIndex++] = nodesRoot;
|
||||
do
|
||||
{
|
||||
int a = stack[--stackIndex];
|
||||
if (overlap_bv_ray(ray, a))
|
||||
{
|
||||
if (is_leaf(a))
|
||||
{
|
||||
float baryB, baryC;
|
||||
float t = intersect_triangle_ray(ray, a, baryB, baryC);
|
||||
if (t < hit.fraction)
|
||||
{
|
||||
hit.fraction = t;
|
||||
hit.triangle = nodes[a].element_index / 3;
|
||||
hit.b = baryB;
|
||||
hit.c = baryC;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stack[stackIndex++] = nodes[a].right;
|
||||
stack[stackIndex++] = nodes[a].left;
|
||||
}
|
||||
}
|
||||
} while (stackIndex > 0);
|
||||
return hit;
|
||||
}
|
||||
|
||||
int TraceFirstHitTriangleNoPortal(vec3 origin, float tmin, vec3 dir, float tmax, out float tparam)
|
||||
{
|
||||
// Perform segmented tracing to keep the ray AABB box smaller
|
||||
vec3 ray_start = origin;
|
||||
vec3 ray_end = origin + dir * tmax;
|
||||
vec3 ray_dir = dir;
|
||||
float tracedist = tmax;
|
||||
float segmentlen = max(200.0, tracedist / 20.0);
|
||||
for (float t = 0.0; t < tracedist; t += segmentlen)
|
||||
{
|
||||
float segstart = t;
|
||||
float segend = min(t + segmentlen, tracedist);
|
||||
|
||||
RayBBox ray = create_ray(ray_start + ray_dir * segstart, ray_start + ray_dir * segend);
|
||||
TraceHit hit = find_first_hit(ray);
|
||||
if (hit.fraction < 1.0)
|
||||
{
|
||||
tparam = hit.fraction = segstart * (1.0 - hit.fraction) + segend * hit.fraction;
|
||||
return hit.triangle;
|
||||
}
|
||||
}
|
||||
|
||||
tparam = tracedist;
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
int TraceFirstHitTriangleT(vec3 origin, float tmin, vec3 dir, float tmax, out float t)
|
||||
{
|
||||
int primitiveID;
|
||||
while(true)
|
||||
{
|
||||
primitiveID = TraceFirstHitTriangleNoPortal(origin, tmin, dir, tmax, t);
|
||||
|
||||
if(primitiveID < 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
SurfaceInfo surface = surfaces[surfaceIndices[primitiveID]];
|
||||
|
||||
if(surface.PortalIndex == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Portal was hit: Apply transformation onto the ray
|
||||
mat4 transformationMatrix = portals[surface.PortalIndex].Transformation;
|
||||
|
||||
origin = (transformationMatrix * vec4(origin + dir * t, 1.0)).xyz;
|
||||
dir = (transformationMatrix * vec4(dir, 0.0)).xyz;
|
||||
tmax -= t;
|
||||
}
|
||||
return primitiveID;
|
||||
}
|
||||
|
||||
int TraceFirstHitTriangle(vec3 origin, float tmin, vec3 dir, float tmax)
|
||||
{
|
||||
float t;
|
||||
return TraceFirstHitTriangleT(origin, tmin, dir, tmax, t);
|
||||
}
|
||||
|
||||
bool TraceAnyHit(vec3 origin, float tmin, vec3 dir, float tmax)
|
||||
{
|
||||
return TraceFirstHitTriangle(origin, tmin, dir, tmax) >= 0;
|
||||
}
|
||||
|
||||
bool TracePoint(vec3 origin, vec3 target, float tmin, vec3 dir, float tmax)
|
||||
{
|
||||
int primitiveID;
|
||||
float t;
|
||||
while(true)
|
||||
{
|
||||
t = tmax;
|
||||
primitiveID = TraceFirstHitTriangleNoPortal(origin, tmin, dir, tmax, t);
|
||||
|
||||
origin += dir * t;
|
||||
tmax -= t;
|
||||
|
||||
if(primitiveID < 0)
|
||||
{
|
||||
// We didn't hit anything
|
||||
break;
|
||||
}
|
||||
|
||||
SurfaceInfo surface = surfaces[surfaceIndices[primitiveID]];
|
||||
|
||||
if(surface.PortalIndex == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if(dot(surface.Normal, dir) >= 0.0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
mat4 transformationMatrix = portals[surface.PortalIndex].Transformation;
|
||||
origin = (transformationMatrix * vec4(origin, 1.0)).xyz;
|
||||
dir = (transformationMatrix * vec4(dir, 0.0)).xyz;
|
||||
|
||||
#if defined(USE_RAYQUERY)
|
||||
#else
|
||||
origin += dir * tmin;
|
||||
tmax -= tmin;
|
||||
#endif
|
||||
}
|
||||
|
||||
return distance(origin, target) <= 1.0;
|
||||
}
|
46
wadsrc/static/shaders/lightmap/frag_resolve.glsl
Normal file
46
wadsrc/static/shaders/lightmap/frag_resolve.glsl
Normal file
|
@ -0,0 +1,46 @@
|
|||
|
||||
layout(set = 0, binding = 0) uniform sampler2DMS tex;
|
||||
|
||||
layout(location = 0) in vec3 worldpos;
|
||||
layout(location = 0) out vec4 fragcolor;
|
||||
|
||||
vec4 samplePixel(ivec2 pos, int count)
|
||||
{
|
||||
vec4 c = vec4(0.0);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
c += texelFetch(tex, pos, i);
|
||||
}
|
||||
if (c.a > 0.0)
|
||||
c /= c.a;
|
||||
return c;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
int count = textureSamples(tex);
|
||||
ivec2 size = textureSize(tex);
|
||||
ivec2 pos = ivec2(gl_FragCoord.xy);
|
||||
|
||||
vec4 c = samplePixel(pos, count);
|
||||
if (c.a == 0.0)
|
||||
{
|
||||
for (int y = -1; y <= 1; y++)
|
||||
{
|
||||
for (int x = -1; x <= 1; x++)
|
||||
{
|
||||
if (x != 0 || y != 0)
|
||||
{
|
||||
ivec2 pos2;
|
||||
pos2.x = clamp(pos.x + x, 0, size.x - 1);
|
||||
pos2.y = clamp(pos.y + y, 0, size.y - 1);
|
||||
c += samplePixel(pos2, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (c.a > 0.0)
|
||||
c /= c.a;
|
||||
}
|
||||
|
||||
fragcolor = c;
|
||||
}
|
23
wadsrc/static/shaders/lightmap/vert.glsl
Normal file
23
wadsrc/static/shaders/lightmap/vert.glsl
Normal file
|
@ -0,0 +1,23 @@
|
|||
|
||||
layout(push_constant) uniform PushConstants
|
||||
{
|
||||
uint LightStart;
|
||||
uint LightEnd;
|
||||
int SurfaceIndex;
|
||||
int PushPadding1;
|
||||
vec3 LightmapOrigin;
|
||||
float PushPadding2;
|
||||
vec3 LightmapStepX;
|
||||
float PushPadding3;
|
||||
vec3 LightmapStepY;
|
||||
float PushPadding4;
|
||||
};
|
||||
|
||||
layout(location = 0) in vec2 aPosition;
|
||||
layout(location = 0) out vec3 worldpos;
|
||||
|
||||
void main()
|
||||
{
|
||||
worldpos = LightmapOrigin + LightmapStepX * aPosition.x + LightmapStepY * aPosition.y;
|
||||
gl_Position = vec4(aPosition * 2.0 - 1.0, 0.0, 1.0);
|
||||
}
|
Loading…
Reference in a new issue