vulkan fixes (correctness) and something about the valve BSP / dark MIS integration

This commit is contained in:
ecker 2026-09-02 18:28:10 -05:00
parent 5ba9983594
commit df4e9dcb36
25 changed files with 1528 additions and 366 deletions

343
Makefile
View File

@ -1,171 +1,172 @@
# Defaults # Defaults
DEFAULTS_DIR := ./makefiles/defaults DEFAULTS_DIR := ./makefiles/defaults
_mkdir := $(shell mkdir -p $(DEFAULTS_DIR)) _mkdir := $(shell mkdir -p $(DEFAULTS_DIR))
ifeq ($(origin ARCH),undefined) ifeq ($(origin ARCH),undefined)
ARCH := $(shell cat "$(DEFAULTS_DIR)/arch" 2>/dev/null) ARCH := $(shell cat "$(DEFAULTS_DIR)/arch" 2>/dev/null)
endif endif
ifeq ($(strip $(ARCH)),) ifeq ($(strip $(ARCH)),)
ifeq ($(OS),Windows_NT) ifeq ($(OS),Windows_NT)
ARCH := win64 ARCH := win64
else else
UNAME_S := $(shell uname -s) UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux) ifeq ($(UNAME_S),Linux)
ARCH := linux ARCH := linux
else else
$(warning Unknown host '$(UNAME_S)', defaulting ARCH to linux) $(warning Unknown host '$(UNAME_S)', defaulting ARCH to linux)
ARCH := linux ARCH := linux
endif endif
endif endif
_write_arch := $(shell echo -n "$(ARCH)" > "$(DEFAULTS_DIR)/arch") _write_arch := $(shell echo -n "$(ARCH)" > "$(DEFAULTS_DIR)/arch")
endif endif
ifeq ($(origin COMPILER),undefined) ifeq ($(origin COMPILER),undefined)
COMPILER := $(shell cat "$(DEFAULTS_DIR)/cc" 2>/dev/null) COMPILER := $(shell cat "$(DEFAULTS_DIR)/cc" 2>/dev/null)
endif endif
ifeq ($(strip $(COMPILER)),) ifeq ($(strip $(COMPILER)),)
ifneq ($(shell command -v gcc 2>/dev/null),) ifneq ($(shell command -v gcc 2>/dev/null),)
COMPILER := gcc COMPILER := gcc
else ifneq ($(shell command -v clang 2>/dev/null),) else ifneq ($(shell command -v clang 2>/dev/null),)
COMPILER := clang COMPILER := clang
else else
$(warning No gcc or clang found, defaulting COMPILER to gcc) $(warning No gcc or clang found, defaulting COMPILER to gcc)
COMPILER := gcc COMPILER := gcc
endif endif
_write_cc := $(shell echo -n "$(COMPILER)" > "$(DEFAULTS_DIR)/cc") _write_cc := $(shell echo -n "$(COMPILER)" > "$(DEFAULTS_DIR)/cc")
endif endif
# to-do: deduce via existence of Vulkan/OpenGL headers # to-do: deduce via existence of Vulkan/OpenGL headers
ifeq ($(origin RENDERER),undefined) ifeq ($(origin RENDERER),undefined)
RENDERER := $(shell cat "$(DEFAULTS_DIR)/renderer" 2>/dev/null) RENDERER := $(shell cat "$(DEFAULTS_DIR)/renderer" 2>/dev/null)
endif endif
ifeq ($(strip $(RENDERER)),) ifeq ($(strip $(RENDERER)),)
RENDERER := vulkan RENDERER := vulkan
_write_rend := $(shell echo -n "$(RENDERER)" > "$(DEFAULTS_DIR)/renderer") _write_rend := $(shell echo -n "$(RENDERER)" > "$(DEFAULTS_DIR)/renderer")
endif endif
TARGET_NAME = program TARGET_NAME = program
TARGET_EXTENSION = .exe TARGET_EXTENSION = .exe
DLIB_EXTENSION = .dll DLIB_EXTENSION = .dll
SLIB_EXTENSION = .a SLIB_EXTENSION = .a
PREFIX = $(ARCH).$(COMPILER).$(RENDERER) PREFIX = $(ARCH).$(COMPILER).$(RENDERER)
# Basic Paths # Basic Paths
CXX := $(CDIR)$(CXX) CXX := $(CDIR)$(CXX)
BIN_DIR += ./bin BIN_DIR += ./bin
ENGINE_SRC_DIR += ./engine/src ENGINE_SRC_DIR += ./engine/src
ENGINE_INC_DIR += ./engine/inc ENGINE_INC_DIR += ./engine/inc
ENGINE_LIB_DIR += ./engine/lib ENGINE_LIB_DIR += ./engine/lib
DEP_SRC_DIR += ./dep/src DEP_SRC_DIR += ./dep/src
EXT_SRC_DIR += ./ext EXT_SRC_DIR += ./ext
CLIENT_SRC_DIR += ./client CLIENT_SRC_DIR += ./client
# Base Flags # Base Flags
OPTIMIZATIONS = -O3 -fstrict-aliasing -DUF_NO_EXCEPTIONS OPTIMIZATIONS = -O3 -fstrict-aliasing -DUF_NO_EXCEPTIONS
WARNINGS = -Wall -Wno-unknown-pragmas -Wno-unused-function -Wno-unused-variable -Wno-switch -Wno-reorder -Wno-sign-compare -Wno-unused-but-set-variable -Wno-ignored-attributes -Wno-narrowing -Wno-misleading-indentation WARNINGS = -Wall -Wno-unknown-pragmas -Wno-unused-function -Wno-unused-variable -Wno-switch -Wno-reorder -Wno-sign-compare -Wno-unused-but-set-variable -Wno-ignored-attributes -Wno-narrowing -Wno-misleading-indentation
FLAGS += -std=c++2b $(OPTIMIZATIONS) $(WARNINGS) -fdiagnostics-color=always FLAGS += -std=c++2b $(OPTIMIZATIONS) $(WARNINGS) -fdiagnostics-color=always
# Base Library Definitions # Base Library Definitions
LIB_NAME += uf LIB_NAME += uf
EXT_LIB_NAME += ext EXT_LIB_NAME += ext
PREFIX_PATH = $(ARCH)/$(COMPILER)/$(RENDERER) PREFIX_PATH = $(ARCH)/$(COMPILER)/$(RENDERER)
INC_DIR += $(ENGINE_INC_DIR) INC_DIR += $(ENGINE_INC_DIR)
LIB_DIR += $(ENGINE_LIB_DIR) LIB_DIR += $(ENGINE_LIB_DIR)
INCS += -I$(ENGINE_INC_DIR) -I./dep/include/ INCS += -I$(ENGINE_INC_DIR) -I./dep/include/
LIBS += -L$(ENGINE_LIB_DIR) -L$(LIB_DIR)/$(PREFIX_PATH)/ -L$(LIB_DIR)/$(ARCH)/$(COMPILER)/ -L$(LIB_DIR)/$(ARCH)/ LIBS += -L$(ENGINE_LIB_DIR) -L$(LIB_DIR)/$(PREFIX_PATH)/ -L$(LIB_DIR)/$(ARCH)/$(COMPILER)/ -L$(LIB_DIR)/$(ARCH)/
LINKS += $(UF_LIBS) $(EXT_LIBS) $(DEPS) LINKS += $(UF_LIBS) $(EXT_LIBS) $(DEPS)
# DLL # DLL
SRCS_DLL := $(shell find $(ENGINE_SRC_DIR) -name "*.cpp") $(shell find $(DEP_SRC_DIR) -name "*.cpp") SRCS_DLL := $(shell find $(ENGINE_SRC_DIR) -name "*.cpp") $(shell find $(DEP_SRC_DIR) -name "*.cpp")
SRCS_DLL_C := $(shell find $(ENGINE_SRC_DIR) -name "*.c") $(shell find $(DEP_SRC_DIR) -name "*.c") SRCS_DLL_C := $(shell find $(ENGINE_SRC_DIR) -name "*.c") $(shell find $(DEP_SRC_DIR) -name "*.c")
OBJS_DLL += $(patsubst %.cpp,%.$(PREFIX).o,$(SRCS_DLL)) $(patsubst %.c,%.$(PREFIX).o,$(SRCS_DLL_C)) OBJS_DLL += $(patsubst %.cpp,%.$(PREFIX).o,$(SRCS_DLL)) $(patsubst %.c,%.$(PREFIX).o,$(SRCS_DLL_C))
BASE_DLL += lib$(LIB_NAME) BASE_DLL += lib$(LIB_NAME)
IM_DLL += $(ENGINE_LIB_DIR)/$(PREFIX_PATH)/$(BASE_DLL)$(DLIB_EXTENSION) IM_DLL += $(ENGINE_LIB_DIR)/$(PREFIX_PATH)/$(BASE_DLL)$(DLIB_EXTENSION)
EX_DLL += $(BIN_DIR)/exe/lib/$(PREFIX_PATH)/$(BASE_DLL)$(DLIB_EXTENSION) EX_DLL += $(BIN_DIR)/exe/lib/$(PREFIX_PATH)/$(BASE_DLL)$(DLIB_EXTENSION)
EXT_IM_DLL += $(ENGINE_LIB_DIR)/$(PREFIX_PATH)/$(BASE_EXT_DLL)$(DLIB_EXTENSION) EXT_IM_DLL += $(ENGINE_LIB_DIR)/$(PREFIX_PATH)/$(BASE_EXT_DLL)$(DLIB_EXTENSION)
EXT_EX_DLL += $(BIN_DIR)/exe/lib/$(PREFIX_PATH)/$(BASE_EXT_DLL)$(DLIB_EXTENSION) EXT_EX_DLL += $(BIN_DIR)/exe/lib/$(PREFIX_PATH)/$(BASE_EXT_DLL)$(DLIB_EXTENSION)
SRCS_EXT_DLL := $(shell find $(EXT_SRC_DIR) -name "*.cpp") SRCS_EXT_DLL := $(shell find $(EXT_SRC_DIR) -name "*.cpp")
OBJS_EXT_DLL += $(patsubst %.cpp,%.$(PREFIX).o,$(SRCS_EXT_DLL)) OBJS_EXT_DLL += $(patsubst %.cpp,%.$(PREFIX).o,$(SRCS_EXT_DLL))
BASE_EXT_DLL += lib$(EXT_LIB_NAME) BASE_EXT_DLL += lib$(EXT_LIB_NAME)
EXT_DEPS += -l$(LIB_NAME) $(DEPS) EXT_DEPS += -l$(LIB_NAME) $(DEPS)
EXT_INC_DIR += $(INC_DIR) EXT_INC_DIR += $(INC_DIR)
EXT_INCS += $(INCS) EXT_INCS += $(INCS)
EXT_LIBS += $(LIBS) EXT_LIBS += $(LIBS)
EXT_LINKS += $(UF_LIBS) $(EXT_LIBS) $(EXT_DEPS) EXT_LINKS += $(UF_LIBS) $(EXT_LIBS) $(EXT_DEPS)
# Executable # Executable
SRCS := $(shell find $(CLIENT_SRC_DIR) -name "*.cpp") SRCS := $(shell find $(CLIENT_SRC_DIR) -name "*.cpp")
OBJS += $(patsubst %.cpp,%.$(PREFIX).o,$(SRCS)) OBJS += $(patsubst %.cpp,%.$(PREFIX).o,$(SRCS))
TARGET += $(BIN_DIR)/exe/$(TARGET_NAME).$(PREFIX)$(TARGET_EXTENSION) TARGET += $(BIN_DIR)/exe/$(TARGET_NAME).$(PREFIX)$(TARGET_EXTENSION)
# Shaders # Shaders
SRCS_SHADERS := $(shell find bin/data/shaders/ -name "*.glsl") SRCS_SHADERS := $(shell find bin/data/shaders/ -name "*.glsl")
TARGET_SHADERS += $(patsubst %.glsl,%.spv,$(SRCS_SHADERS)) TARGET_SHADERS += $(patsubst %.glsl,%.spv,$(SRCS_SHADERS))
.DEFAULT_GOAL := $(PREFIX) .DEFAULT_GOAL := $(PREFIX)
.PHONY: $(PREFIX) clean run run-debug clean-shaders backup .PHONY: $(PREFIX) clean run run-debug clean-shaders backup
.FORCE: .FORCE:
include makefiles/platforms/$(ARCH).$(COMPILER).mk include makefiles/platforms/$(ARCH).$(COMPILER).mk
ifneq (,$(findstring win64,$(ARCH))) ifneq (,$(findstring win64,$(ARCH)))
include makefiles/platforms/win64.mk include makefiles/platforms/win64.mk
else ifneq (,$(findstring linux,$(ARCH))) else ifneq (,$(findstring linux,$(ARCH)))
include makefiles/platforms/linux.mk include makefiles/platforms/linux.mk
else ifneq (,$(findstring dreamcast,$(ARCH))) else ifneq (,$(findstring dreamcast,$(ARCH)))
include makefiles/platforms/dreamcast.mk include makefiles/platforms/dreamcast.mk
endif endif
include makefiles/dependencies.mk include makefiles/dependencies.mk
# Build Rules # Build Rules
$(PREFIX): $(EX_DLL) $(EXT_EX_DLL) $(TARGET) $(TARGET_SHADERS) $(PREFIX): $(EX_DLL) $(EXT_EX_DLL) $(TARGET) $(TARGET_SHADERS)
%.$(PREFIX).o: %.cpp %.$(PREFIX).o: %.cpp
$(CXX) $(FLAGS) $(INCS) -c $< -o $@ $(CXX) $(FLAGS) $(INCS) -c $< -o $@
%.$(PREFIX).o: %.c %.$(PREFIX).o: %.c
$(CC) $(FLAGS) $(INCS) -c $< -o $@ $(CC) $(FLAGS) $(INCS) -c $< -o $@
ifneq ($(ARCH),dreamcast) ifneq ($(ARCH),dreamcast)
$(TARGET): $(OBJS) $(TARGET): $(OBJS)
$(CXX) $(FLAGS) $(OBJS) $(LIBS) $(INCS) $(LINKS) -l$(LIB_NAME) -l$(EXT_LIB_NAME) -o $(TARGET) $(CXX) $(FLAGS) $(OBJS) $(LIBS) $(INCS) $(LINKS) -l$(LIB_NAME) -l$(EXT_LIB_NAME) -o $(TARGET)
endif endif
%.spv: %.glsl %.spv: %.glsl
$(GLSLC) --target-env=vulkan1.2 -o $@ $< $(GLSLC) --target-env=vulkan1.2 -o $@ $<
@-$(SPV_LINTER) $@ @-$(SPV_LINTER) $@
@-$(SPV_OPTIMIZER) --preserve-bindings --preserve-spec-constants -O $@ -o $@ @-$(SPV_OPTIMIZER) --preserve-bindings --preserve-spec-constants -O $@ -o $@
shaders: $(TARGET_SHADERS) shaders: $(TARGET_SHADERS)
clean: clean:
@-rm $(EX_DLL) @-rm $(EX_DLL)
@-rm $(EXT_EX_DLL) @-rm $(EXT_EX_DLL)
@-rm $(TARGET) @-rm $(TARGET)
@-rm -f $(OBJS_DLL) @-rm -f $(OBJS_DLL)
@-rm -f $(OBJS_EXT_DLL) @-rm -f $(OBJS_EXT_DLL)
@-rm -f $(OBJS) @-rm -f $(OBJS)
clean-shaders: clean-shaders:
@-rm -f $(TARGET_SHADERS) @-rm -f $(TARGET_SHADERS)
run: run:
@echo -n $(ARCH) > "./bin/exe/default/arch" @echo -n $(ARCH) > "./bin/exe/default/arch"
@echo -n $(COMPILER) > "./bin/exe/default/cc" @echo -n $(COMPILER) > "./bin/exe/default/cc"
@echo -n $(RENDERER) > "./bin/exe/default/renderer" @echo -n $(RENDERER) > "./bin/exe/default/renderer"
./program.sh ./program.sh
run-debug: run-debug:
@echo -n $(ARCH) > "./bin/exe/default/arch" @echo -n $(ARCH) > "./bin/exe/default/arch"
@echo -n $(COMPILER) > "./bin/exe/default/cc" @echo -n $(COMPILER) > "./bin/exe/default/cc"
@echo -n $(RENDERER) > "./bin/exe/default/renderer" @echo -n $(RENDERER) > "./bin/exe/default/renderer"
./debug.sh ./debug.sh

View File

@ -1,89 +1,509 @@
_G.OSM = _G.OSM or {} _G.OSM = _G.OSM or {}
_G.DarkUtils = _G.DarkUtils or {}
_G.OSM["TweqLockedButton"] = { _G.DarkQB = _G.DarkQB or {}
onMessage = function(entity, payload, dMeta) end,
_G.OSM_States = _G.OSM_States or {}
onFrob = function(entity, payload, dMeta) local function getState( entity )
local eName = entity:name() or entity:uid() local uid = entity:uid()
-- todo: deduce lock state _G.OSM_States[uid] = _G.OSM_States[uid] or {}
local isLocked = true return _G.OSM_States[uid]
end
print(entity, "is locked?", isLocked)
-- play a frob/activate sound for an object, falling back through tag queries
if isLocked then local function playActivateSound( entity, dMeta )
-- print(string.format("%s is locked! Emitting 'cardfail'.", eName)) local tags = dMeta["class_tags"] or ""
_G.DarkUtils.playSound(entity, "", "cardfail", { spatial = true, maxDistance = 15.0 }) if tags ~= "" then tags = tags .. ", " end
-- entity:callHook("ui:FlashMessage", { text = "Access Required: {}" }) local played = _G.DarkUtils.playSound( entity, tags .. "Event Activate", nil, { spatial = true, maxDistance = 15.0 } )
else if not played then played = _G.DarkUtils.playSound( entity, tags .. "Event StateChange", nil, { spatial = true, maxDistance = 15.0 } ) end
-- unlock logic if not played then _G.DarkUtils.playSound( entity, dMeta["class_tags"] or "", nil, { spatial = true, maxDistance = 15.0 } ) end
local cTags = (dMeta["class_tags"] or "") .. ", Event StateChange" end
_G.DarkUtils.playSound(entity, cTags, "", { spatial = true, maxDistance = 15.0 })
local function broadcast( entity, dMeta, message, caller, flavors )
entity:callHook("link:Broadcast.%UID%", { entity:callHook( "link:Broadcast.%UID%", {
message = "TurnOn", flavors = { "ControlDevice" }, caller = payload.user, callerDarkID = dMeta["id"] message = message,
}) flavors = flavors or { "ControlDevice", "SwitchLink" },
end caller = caller or entity:uid(),
end callerDarkID = dMeta["id"]
} })
end
_G.OSM["RelayTrap"] = function(entity, payload, dMeta) -- resolve a dark object id to an entity
local msg = payload.message local function getTarget( darkID )
entity:callHook("link:Broadcast.%UID%", { local targetUID = _G.DarkTargets and _G.DarkTargets[darkID]
message = msg, if not targetUID then return nil end
flavors = { "ControlDevice", "SwitchLink" }, local targetEnt = entities.get(targetUID)
caller = entity:uid(), if targetEnt and targetEnt:uid() then return targetEnt end
callerDarkID = payload.callerDarkID return nil
}) end
end
-- find the first connection matching a flavor substring, returns (connection, target entity)
_G.OSM["TrapQBFilter"] = _G.OSM["RelayTrap"] local function findConnection( dMeta, flavorMatch )
_G.OSM["TrapQBNegFilter"] = _G.OSM["RelayTrap"] local conns = dMeta["connections"] or {}
_G.OSM["ElevatorButton"] = _G.OSM["RelayTrap"] for i = 1, #conns do
local conn = conns[i]
_G.OSM["RequireAllTrap"] = function(entity, payload, dMeta) if string.find( conn.flavor or "", flavorMatch, 1, true ) then
local msg = payload.message return conn, getTarget( conn.target_id )
local callerDarkID = payload.callerDarkID end
end
_G.RAT_States = _G.RAT_States or {} return nil, nil
local uid = entity:uid() end
_G.RAT_States[uid] = _G.RAT_States[uid] or { inputs = {}, wasOn = false }
local state = _G.RAT_States[uid] -- per-object state cleanup
local ent = ent
if callerDarkID then ent:addHook( "entity:Destroy.%UID%", function()
state.inputs[callerDarkID] = (msg == "TurnOn") if _G.OSM_States[ent:uid()] then _G.OSM_States[ent:uid()] = nil end
end end)
local allOn = true -- relays / logic
local incoming = dMeta["incoming_connections"] or {} _G.OSM["RelayTrap"] = function(entity, payload, dMeta)
for i = 1, #incoming do local msg = payload.message
local conn = incoming[i] broadcast( entity, dMeta, msg, entity:uid(), { "ControlDevice", "SwitchLink" } )
if conn.flavor == "ControlDevice" or conn.flavor == "SwitchLink" then end
if not state.inputs[conn.source_id] then
allOn = false -- forwards any message it receives (including Toggle)
break _G.OSM["TrapRouter"] = _G.OSM["RelayTrap"]
end
end -- relays the first message it receives, then goes inert
end _G.OSM["OnceRouter"] = function(entity, payload, dMeta)
local st = getState( entity )
if allOn and not state.wasOn then if st.onceFired then return end
state.wasOn = true st.onceFired = true
entity:callHook("link:Broadcast.%UID%", { message = "TurnOn", flavors = { "ControlDevice", "SwitchLink" }, callerDarkID = dMeta["id"], caller = uid }) broadcast( entity, dMeta, payload.message, entity:uid(), { "ControlDevice", "SwitchLink" } )
elseif not allOn and state.wasOn then end
state.wasOn = false
entity:callHook("link:Broadcast.%UID%", { message = "TurnOff", flavors = { "ControlDevice", "SwitchLink" }, callerDarkID = dMeta["id"], caller = uid }) -- relays a message after a delay (seconds; override with dark metadata "delay")
end _G.OSM["TrapDelay"] = function(entity, payload, dMeta)
end local msg = payload.message
if not msg then return end
_G.OSM["BaseElevator"] = function(entity, payload, dMeta) local delay = tonumber( dMeta["delay"] ) or 2.0
local msg = payload.message entity:queueHook( "link:Broadcast.%UID%", {
local eName = entity:name() or entity:uid() message = msg,
flavors = { "ControlDevice", "SwitchLink" },
-- print(string.format("%s (Elevator) received command: %s", eName, msg)) caller = entity:uid(),
callerDarkID = dMeta["id"]
if msg == "TurnOn" or msg == "TurnOff" then }, delay )
-- print(string.format("--> Commanding Lift '%s' to move!", eName)) end
end
end -- quest bit (QB) helpers. Bits are keyed by the dark id of the object that
-- set them; filters gate on the bits of their incoming sources.
_G.OSM["Elevator"] = _G.OSM["BaseElevator"] local function qbSet( key, value )
if value then _G.DarkQB[key] = true else _G.DarkQB[key] = nil end
end
local function qbSatisfied( dMeta, negative )
local incoming = dMeta["incoming_connections"] or {}
if #incoming == 0 then return true end -- fail open if unwired
for i = 1, #incoming do
local conn = incoming[i]
local key = tostring( dMeta["qb"] or conn.source_id )
if _G.DarkQB[key] then return not negative end
end
return negative
end
-- sets a quest bit when triggered, then relays the message
_G.OSM["TrapQBSet"] = function(entity, payload, dMeta)
local msg = payload.message
local key = tostring( dMeta["id"] )
if msg == "TurnOn" then qbSet( key, true )
elseif msg == "TurnOff" then qbSet( key, false ) end
broadcast( entity, dMeta, msg, entity:uid(), { "ControlDevice", "SwitchLink" } )
end
-- sets a quest bit when frobb'd
_G.OSM["FrobQB"] = {
onFrob = function(entity, payload, dMeta)
qbSet( tostring( dMeta["id"] ), true )
playActivateSound( entity, dMeta )
broadcast( entity, dMeta, "TurnOn", payload.user )
end
}
-- only relays while its quest bit is set
_G.OSM["TrapQBFilter"] = function(entity, payload, dMeta)
if not qbSatisfied( dMeta, false ) then return end
broadcast( entity, dMeta, payload.message, entity:uid(), { "ControlDevice", "SwitchLink" } )
end
-- only relays while its quest bit is unset
_G.OSM["TrapQBNegFilter"] = function(entity, payload, dMeta)
if not qbSatisfied( dMeta, true ) then return end
broadcast( entity, dMeta, payload.message, entity:uid(), { "ControlDevice", "SwitchLink" } )
end
-- relays only once all of its control inputs are on
_G.OSM["RequireAllTrap"] = function(entity, payload, dMeta)
local msg = payload.message
local callerDarkID = payload.callerDarkID
_G.RAT_States = _G.RAT_States or {}
local uid = entity:uid()
_G.RAT_States[uid] = _G.RAT_States[uid] or { inputs = {}, wasOn = false }
local state = _G.RAT_States[uid]
if callerDarkID then
state.inputs[callerDarkID] = (msg == "TurnOn")
end
local allOn = true
local incoming = dMeta["incoming_connections"] or {}
for i = 1, #incoming do
local conn = incoming[i]
if conn.flavor == "ControlDevice" or conn.flavor == "SwitchLink" then
if not state.inputs[conn.source_id] then
allOn = false
break
end
end
end
if allOn and not state.wasOn then
state.wasOn = true
entity:callHook("link:Broadcast.%UID%", { message = "TurnOn", flavors = { "ControlDevice", "SwitchLink" }, callerDarkID = dMeta["id"], caller = uid })
elseif not allOn and state.wasOn then
state.wasOn = false
entity:callHook("link:Broadcast.%UID%", { message = "TurnOff", flavors = { "ControlDevice", "SwitchLink" }, callerDarkID = dMeta["id"], caller = uid })
end
end
-- buttons / tweqs
_G.OSM["TweqLockedButton"] = {
onMessage = function(entity, payload, dMeta) end,
onFrob = function(entity, payload, dMeta)
local eName = entity:name() or entity:uid()
-- todo: deduce lock state
local isLocked = true
print(entity, "is locked?", isLocked)
if isLocked then
-- print(string.format("%s is locked! Emitting 'cardfail'.", eName))
_G.DarkUtils.playSound(entity, "", "cardfail", { spatial = true, maxDistance = 15.0 })
-- entity:callHook("ui:FlashMessage", { text = "Access Required: {}" })
else
-- unlock logic
local cTags = (dMeta["class_tags"] or "") .. ", Event StateChange"
_G.DarkUtils.playSound(entity, cTags, "", { spatial = true, maxDistance = 15.0 })
entity:callHook("link:Broadcast.%UID%", {
message = "TurnOn", flavors = { "ControlDevice" }, caller = payload.user, callerDarkID = dMeta["id"]
})
end
end
}
-- basic button: frob turns it on (one-shot)
_G.OSM["BaseButton"] = {
onFrob = function(entity, payload, dMeta)
playActivateSound( entity, dMeta )
broadcast( entity, dMeta, "TurnOn", payload.user )
end
}
-- toggle button: alternates TurnOn / TurnOff on each frob
_G.OSM["TwoStateButton"] = {
onFrob = function(entity, payload, dMeta)
local st = getState( entity )
st.isOn = not st.isOn
playActivateSound( entity, dMeta )
broadcast( entity, dMeta, st.isOn and "TurnOn" or "TurnOff", payload.user )
end
}
-- depressable tweq: frob latches it on
_G.OSM["TweqDepressable"] = {
onFrob = function(entity, payload, dMeta)
playActivateSound( entity, dMeta )
broadcast( entity, dMeta, "TurnOn", payload.user )
end
}
-- toggle tweq: frob or message flips its state and relays it
_G.OSM["TrapTweq"] = {
onMessage = function(entity, payload, dMeta)
local st = getState( entity )
st.isOn = (payload.message == "TurnOn")
broadcast( entity, dMeta, payload.message, payload.caller or entity:uid(), { "ControlDevice", "SwitchLink" } )
end,
onFrob = function(entity, payload, dMeta)
local st = getState( entity )
st.isOn = not st.isOn
playActivateSound( entity, dMeta )
broadcast( entity, dMeta, st.isOn and "TurnOn" or "TurnOff", payload.user )
end
}
-- doors
-- standard door: frob toggles it open/closed (door.lua performs the motion)
_G.OSM["StdDoor"] = {
onMessage = function(entity, payload, dMeta)
local st = getState( entity )
if payload.message == "TurnOn" then st.isOpen = true
elseif payload.message == "TurnOff" then st.isOpen = false end
end,
onFrob = function(entity, payload, dMeta)
local st = getState( entity )
if st.isOpen == nil then
st.isOpen = (tonumber( (dMeta["door"] or {})["status"] ) == 1)
end
st.isOpen = not st.isOpen
playActivateSound( entity, dMeta )
broadcast( entity, dMeta, st.isOpen and "TurnOn" or "TurnOff", payload.user )
end
}
-- lights
-- find the light child node created by the level loader ("{name}_light")
local function getLightComponent( entity )
local lightEnt = entity:findByName( (entity:name() or "") .. "_light" )
if not lightEnt then return nil end
return lightEnt:getComponent("LightBehavior::Metadata")
end
local function setLightPower( entity, on )
local st = getState( entity )
local lightComp = getLightComponent( entity )
if not lightComp then return end
if st.lightPower == nil then st.lightPower = lightComp.power end
lightComp.power = on and st.lightPower or 0.0
end
-- switchable light: TurnOn/TurnOff (or frob) toggles its power
_G.OSM["BaseLight"] = {
onMessage = function(entity, payload, dMeta)
if payload.message == "TurnOn" then setLightPower( entity, true )
elseif payload.message == "TurnOff" then setLightPower( entity, false ) end
end,
onFrob = function(entity, payload, dMeta)
local st = getState( entity )
st.isOn = not st.isOn
playActivateSound( entity, dMeta )
setLightPower( entity, st.isOn )
broadcast( entity, dMeta, st.isOn and "TurnOn" or "TurnOff", payload.user )
end
}
-- light that hums while it is on
_G.OSM["LightSoundOn"] = {
onMessage = function(entity, payload, dMeta)
if payload.message == "TurnOn" then
setLightPower( entity, true )
local st = getState( entity )
if not st.isSounding then
local soundMeta = dMeta["sound"] or {}
local explicitSchema = soundMeta["schema"] or ""
local played = _G.DarkUtils.playSound( entity, "", explicitSchema, { spatial = true, loop = true, unique = true } )
if not played then
played = _G.DarkUtils.playSound( entity, (dMeta["class_tags"] or "") .. ", Event Activate", nil, { spatial = true, loop = true, unique = true } )
end
st.isSounding = played ~= nil
end
elseif payload.message == "TurnOff" then
setLightPower( entity, false )
local st = getState( entity )
if st.isSounding then
st.isSounding = nil
entity:callHook( "sound:Stop.%UID%", {} )
end
end
end,
onFrob = function(entity, payload, dMeta)
local st = getState( entity )
st.isOn = not st.isOn
playActivateSound( entity, dMeta )
setLightPower( entity, st.isOn )
broadcast( entity, dMeta, st.isOn and "TurnOn" or "TurnOff", payload.user )
end
}
-- gravity zones
local function gravityTick( self, factor )
local body = self:getComponent("PhysicsBody")
if not body or not body:initialized() then return end
local st = getState( self )
local affected = st.gravAffected or {}
st.gravAffected = affected
local current = {}
local events = body:getCollisionEvents()
for i = 1, #events do
local event = events[i]
local otherBody
if event.a:getObject():uid() == self:uid() then
otherBody = event.b
elseif event.b:getObject():uid() == self:uid() then
otherBody = event.a
end
if otherBody then
local otherEnt = otherBody:getObject()
local uid = otherEnt:uid()
if uid ~= self:uid() and otherBody:getMass() > 0.0 then
current[uid] = true
if not affected[uid] then
otherBody:setGravity( Vector3f( 0, -9.81 * factor, 0 ) )
affected[uid] = true
end
end
end
end
for uid, _ in pairs( affected ) do
if not current[uid] then
local otherEnt = entities.get(uid)
if otherEnt and otherEnt:uid() then
local otherBody = otherEnt:getComponent("PhysicsBody")
if otherBody and otherBody:initialized() then
otherBody:enableGravity( true )
end
end
affected[uid] = nil
end
end
end
-- destruction / teleport / message traps
local function destroyTick( self )
local body = self:getComponent("PhysicsBody")
if not body or not body:initialized() then return end
local events = body:getCollisionEvents()
for i = 1, #events do
local event = events[i]
local otherBody
if event.a:getObject():uid() == self:uid() then
otherBody = event.b
elseif event.b:getObject():uid() == self:uid() then
otherBody = event.a
end
if otherBody then
local otherEnt = otherBody:getObject()
if otherEnt:name() ~= "Player" and otherBody:getMass() > 0.0 then
entities.destroy( otherEnt )
end
end
end
end
-- destroys dynamic objects that touch it
_G.OSM["TriggerDestroy"] = function(entity, payload, dMeta) end
_G.OSM["TrapDestroyer"] = _G.OSM["TriggerDestroy"]
_G.OSM["TrapDestroy"] = _G.OSM["TriggerDestroy"]
-- on trigger, destroys every object whose name matches its target connection
local function destroyAllMatching( entity, dMeta )
local conn, _ = findConnection( dMeta, "" )
if not conn or not conn.target_node then return end
local pattern = conn.target_node
for i, victim in ipairs( entities.all() ) do
if victim and victim:uid() and victim:uid() ~= entity:uid() and victim:name() == pattern then
entities.destroy( victim )
end
end
end
_G.OSM["TrapTerminator"] = {
onMessage = function(entity, payload, dMeta)
if payload.message == "TurnOn" or payload.message == "Toggle" then
destroyAllMatching( entity, dMeta )
end
end,
onFrob = function(entity, payload, dMeta)
playActivateSound( entity, dMeta )
destroyAllMatching( entity, dMeta )
end
}
_G.OSM["DestroyAllByName"] = _G.OSM["TrapTerminator"]
-- teleports the player to its teleport target on trigger or frob
local function teleportPlayer( entity, dMeta )
local conn, targetEnt = findConnection( dMeta, "Tele" )
if not targetEnt then return end
local player = entities.controller()
if not player or not player:uid() then return end
local playerTransform = player:getComponent("Transform")
local targetTransform = targetEnt:getComponent("Transform")
playerTransform.position = targetTransform.position
playerTransform.orientation = targetTransform.orientation
end
_G.OSM["TrapTeleport"] = {
onMessage = function(entity, payload, dMeta)
if payload.message == "TurnOn" or payload.message == "Toggle" then
teleportPlayer( entity, dMeta )
end
end,
onFrob = function(entity, payload, dMeta)
playActivateSound( entity, dMeta )
teleportPlayer( entity, dMeta )
end
}
-- flashes a text message to the player on trigger or frob
_G.OSM["TrapMessage"] = {
onMessage = function(entity, payload, dMeta)
local text = dMeta["message"] or (entity:name() or "")
print( "[OSM] TrapMessage: " .. tostring(text) )
entity:callHook( "ui:FlashMessage", { text = text } )
end,
onFrob = function(entity, payload, dMeta)
local text = dMeta["message"] or (entity:name() or "")
print( "[OSM] TrapMessage: " .. tostring(text) )
entity:callHook( "ui:FlashMessage", { text = text } )
end
}
-- elevators
_G.OSM["ElevatorButton"] = _G.OSM["RelayTrap"]
_G.OSM["RerouteElevatorButton"] = _G.OSM["RelayTrap"]
_G.OSM["BaseElevator"] = function(entity, payload, dMeta)
local msg = payload.message
local eName = entity:name() or entity:uid()
-- print(string.format("%s (Elevator) received command: %s", eName, msg))
if msg == "TurnOn" or msg == "TurnOff" then
-- print(string.format("--> Commanding Lift '%s' to move!", eName))
end
end
_G.OSM["Elevator"] = _G.OSM["BaseElevator"]
_G.OSM["OldStyleBaseElevator"] = _G.OSM["BaseElevator"]
-- case variants found in level data (to-do: case insensitive mappings?)
_G.OSM["basebutton"] = _G.OSM["BaseButton"]
_G.OSM["trapdelay"] = _G.OSM["TrapDelay"]
_G.OSM["triggerdestroy"] = _G.OSM["TriggerDestroy"]
_G.OSM["Trapterminator"] = _G.OSM["TrapTerminator"]
_G.OSM["LIghtSoundOn"] = _G.OSM["LightSoundOn"]
_G.OSM["tweqtrap"] = _G.OSM["TrapTweq"]
_G.OSM["tweqbutton"] = _G.OSM["BaseButton"]
_G.OSM["Tweqable"] = _G.OSM["TrapTweq"]
-- per-object tick binding (only for scripts that need one)
local metadata = ent:getComponent("Metadata")
local darkMeta = metadata["dark"] or {}
local myScripts = darkMeta["scripts"] or {}
for i = 1, #myScripts do
local script = myScripts[i]
if script == "TrapGravity" or script == "ZeroGravRoom" then
local factor = tonumber( darkMeta["gravity"] )
if factor == nil then factor = 0.0 end
ent:bind( "tick", function(self) gravityTick( self, factor ) end )
elseif script == "TriggerDestroy" or script == "triggerdestroy" or script == "TrapDestroyer" or script == "TrapDestroy" then
ent:bind( "tick", function(self) destroyTick( self ) end )
end
end

View File

@ -0,0 +1,42 @@
local ent = ent
local metadata = ent:getComponent("Metadata")
local metadataValve = metadata["valve"] or {}
local physicsBody = ent:getComponent("PhysicsBody")
local health = tonumber(metadataValve["health"]) or 100.0
local flags = tonumber(metadataValve["spawnflags"]) or 0
local unbreakable = (math.floor(flags / 1) % 2) ~= 0
local damageScale = tonumber(metadataValve["damagescale"]) or 1.0
local alive = true
local function destroy()
if not alive then return end
alive = false
ent:queueHook("io:FireOutput.%UID%", { output = "OnBreak" }, 0)
entities.destroy(ent)
end
ent:bind( "tick", function(self)
if not alive or unbreakable then return end
if not physicsBody:initialized() then return end
local collisionEvents = physicsBody:getCollisionEvents()
for i, event in ipairs(collisionEvents) do
if event.impulse > 1.0 then
health = health - (event.impulse * damageScale)
if health <= 0 then
destroy()
break
end
end
end
end )
ent:addHook("io:Input.%UID%", function( payload )
local input = payload.input
if input == "Kill" or input == "Break" then
destroy()
end
end)

View File

@ -0,0 +1,124 @@
local ent = ent
local metadata = ent:getComponent("Metadata")
local metadataValve = metadata["valve"] or {}
local timer = Timer.new()
if not timer:running() then
timer:start()
end
local SOURCE_TO_METERS = 0.07
local transform = ent:getComponent("Transform")
local physicsBody = ent:getComponent("PhysicsBody")
local lip = (tonumber(metadataValve["lip"]) or 8.0) * SOURCE_TO_METERS
local wait = tonumber(metadataValve["wait"]) or 1.0
local flags = tonumber(metadataValve["spawnflags"]) or 0
-- movement direction: movedir vector, else angle keyvalue, else local up
local moveDir = Vector3f(0, 1, 0)
local movedir = metadataValve["movedir"]
if type(movedir) == "table" then
moveDir = Vector3f(movedir[1], movedir[2], movedir[3]):normalize()
elseif type(movedir) == "string" and movedir ~= "" then
local x, y, z = movedir:match("^%s*(-?%d+%.?%d*)%s+(-?%d+%.?%d*)%s+(-?%d+%.?%d*)%s*$")
if x then
moveDir = Vector3f(tonumber(x), tonumber(y), tonumber(z)):normalize()
end
else
local angle = tonumber(metadataValve["angle"])
if angle then
if angle == -1 then
moveDir = Vector3f(0, 1, 0)
elseif angle == -2 then
moveDir = Vector3f(0, -1, 0)
else
local yaw = math.rad(angle)
moveDir = Vector3f(math.cos(yaw), 0.0, -math.sin(yaw))
end
end
end
local pressTime = 0.5
if wait > 0 then
pressTime = math.max(0.25, math.min(wait, 2.0))
end
local speed = lip / pressTime
-- 0 = idle, 1 = pressing out, 2 = held, 3 = retracting
local state = 0
local currentDistance = 0
local function press()
if state ~= 0 then return end
state = 1
ent:queueHook("io:FireOutput.%UID%", { output = "OnPressed" }, 0)
ent:queueHook("io:FireOutput.%UID%", { output = "OnUsed" }, 0)
end
local function release()
if state == 2 then
state = 3
end
end
ent:bind( "tick", function(self)
if state == 1 then
local remaining = lip - currentDistance
local move = math.min(time.delta() * speed, remaining)
currentDistance = currentDistance + move
transform.position = transform.position + moveDir * move
if currentDistance >= lip then
state = 2
timer:reset()
end
elseif state == 3 then
local move = math.min(time.delta() * speed, currentDistance)
currentDistance = currentDistance - move
transform.position = transform.position - moveDir * move
if currentDistance <= 0 then
state = 0
ent:queueHook("io:FireOutput.%UID%", { output = "OnUnpressed" }, 0)
end
elseif state == 2 and wait > 0 then
if timer:elapsed() >= wait then
state = 3
end
end
-- press when touched (floor buttons)
if state == 0 and physicsBody:initialized() then
local collisionEvents = physicsBody:getCollisionEvents()
for i, event in ipairs(collisionEvents) do
local other = nil
if event.a:getObject():uid() == ent:uid() then
other = event.b
elseif event.b:getObject():uid() == ent:uid() then
other = event.a
end
if other then
press()
break
end
end
end
end )
ent:addHook( "entity:Use.%UID%", function( payload )
if payload.user == ent:uid() then return end
press()
end )
ent:addHook("io:Input.%UID%", function( payload )
local input = payload.input
if input == "Press" or input == "Down" then
press()
elseif input == "Release" or input == "Up" or input == "Reset" then
release()
end
end)

View File

@ -0,0 +1,25 @@
local ent = ent
local metadata = ent:getComponent("Metadata")
local metadataValve = metadata["valve"] or {}
local cases = {}
for i = 1, 20 do
local value = metadataValve["Case" .. i]
if value ~= nil and tostring(value) ~= "" then
cases[i] = tostring(value)
end
end
ent:addHook("io:Input.%UID%", function( payload )
local param = payload.parameter
local p = (param == nil) and "" or tostring(param)
for i = 1, 20 do
if cases[i] then
if cases[i] == p or tonumber(cases[i]) == tonumber(p) then
ent:callHook("io:FireOutput.%UID%", { output = "Case" .. i })
break
end
end
end
end)

View File

@ -0,0 +1,37 @@
local ent = ent
local metadata = ent:getComponent("Metadata")
local metadataValve = metadata["valve"] or {}
local startCount = tonumber(metadataValve["StartCount"]) or 0
local tripCount = tonumber(metadataValve["TripCount"]) or 10
local flags = tonumber(metadataValve["spawnflags"]) or 0
local enabled = (math.floor(flags / 1) % 2) == 0
local count = startCount
ent:addHook("io:Input.%UID%", function( payload )
if not enabled then return end
local input = payload.input
if input == "Increment" then
count = count + 1
if count >= tripCount then
ent:callHook("io:FireOutput.%UID%", { output = "OnHigh" })
count = startCount
end
elseif input == "Decrement" then
count = count - 1
if count < startCount then
ent:callHook("io:FireOutput.%UID%", { output = "OnLow" })
count = startCount
end
elseif input == "Reset" then
count = startCount
elseif input == "Set" then
local value = tonumber(payload.parameter)
if value then
count = math.floor(value)
end
end
end)

View File

@ -0,0 +1,56 @@
local ent = ent
local metadata = ent:getComponent("Metadata")
local metadataValve = metadata["valve"] or {}
local physicsBody = ent:getComponent("PhysicsBody")
local damage = tonumber(metadataValve["damage"]) or 1.0
local delay = tonumber(metadataValve["delay"]) or 0.5
-- note: the engine has no health/damage system yet, so this trigger only
-- reports contact via I/O outputs (OnEntityTouch / OnTrigger / OnEndTouch)
local touching = {}
local function entityKey( body )
local object = body:getObject()
local meta = object:getComponent("Metadata")
local valve = meta["valve"] or {}
if valve["targetname"] then
return tostring(valve["targetname"])
end
return tostring(object:uid())
end
ent:bind( "tick", function(self)
if not physicsBody:initialized() then return end
local currentCollisions = {}
local collisionEvents = physicsBody:getCollisionEvents()
for i, event in ipairs(collisionEvents) do
local other = nil
if event.a:getObject():uid() == ent:uid() then
other = event.b
elseif event.b:getObject():uid() == ent:uid() then
other = event.a
end
if other then
local key = entityKey(other)
currentCollisions[key] = true
if not touching[key] then
touching[key] = true
ent:queueHook("io:FireOutput.%UID%", { output = "OnEntityTouch", parameter = key }, 0)
ent:queueHook("io:FireOutput.%UID%", { output = "OnTrigger" }, 0)
end
end
end
for key, _ in pairs(touching) do
if not currentCollisions[key] then
touching[key] = nil
ent:queueHook("io:FireOutput.%UID%", { output = "OnEndTouch", parameter = key }, 0)
end
end
end )

View File

@ -0,0 +1,34 @@
local ent = ent
local metadata = ent:getComponent("Metadata")
local metadataValve = metadata["valve"] or {}
local physicsBody = ent:getComponent("PhysicsBody")
local controller = entities.controller()
local controllerBody = controller:getComponent("PhysicsBody")
local climbSpeed = 2.0
ent:bind( "tick", function(self)
if not physicsBody:initialized() then return end
if not controllerBody:initialized() then return end
local box = physicsBody:bounds()
local pos = controllerBody:getTransform().position
local inside =
pos.x >= box.min.x - 0.5 and pos.x <= box.max.x + 0.5 and
pos.z >= box.min.z - 0.5 and pos.z <= box.max.z + 0.5 and
pos.y >= box.min.y - 0.5 and pos.y <= box.max.y + 1.0
if not inside then return end
local velocity = controllerBody:getVelocity()
if window.keyPressed("Space") or window.keyPressed("W") then
velocity.y = climbSpeed
elseif window.keyPressed("S") then
velocity.y = -climbSpeed
end
controllerBody:setVelocity(velocity)
end )

View File

@ -0,0 +1,43 @@
local ent = ent
local metadata = ent:getComponent("Metadata")
local metadataValve = metadata["valve"] or {}
local timer = Timer.new()
if not timer:running() then
timer:start()
end
local delay = tonumber(metadataValve["delay"]) or 0.0
local enabled = true
local pendingOutputs = {}
ent:addHook("io:Input.%UID%", function( payload )
local input = payload.input
if input == "Enable" then
enabled = true
elseif input == "Disable" then
enabled = false
elseif input == "Toggle" then
enabled = not enabled
else
if not enabled then return end
table.insert(pendingOutputs, {
fireTime = timer:elapsed() + math.max(0.0, delay),
input = input,
parameter = payload.parameter
})
end
end)
ent:bind( "tick", function(self)
for i = #pendingOutputs, 1, -1 do
local job = pendingOutputs[i]
if timer:elapsed() >= job.fireTime then
ent:queueHook("io:FireOutput.%UID%", { output = job.input, parameter = job.parameter }, 0)
table.remove(pendingOutputs, i)
end
end
end )

View File

@ -0,0 +1,55 @@
local ent = ent
local metadata = ent:getComponent("Metadata")
local metadataValve = metadata["valve"] or {}
local timer = Timer.new()
if not timer:running() then
timer:start()
end
local refireTime = tonumber(metadataValve["RefireTime"]) or 10.0
local randomRange = tonumber(metadataValve["random"]) or 0.0
local flags = tonumber(metadataValve["spawnflags"]) or 0
local startDisabled = (tonumber(metadataValve["StartDisabled"]) or 0) ~= 0
if not startDisabled and (math.floor(flags / 1) % 2) ~= 0 then
startDisabled = true
end
local enabled = not startDisabled
local nextFire = refireTime
local function interval()
local value = refireTime
if randomRange > 0 then
value = value + (math.random() * 2.0 - 1.0) * randomRange
end
return math.max(0.01, value)
end
ent:bind( "tick", function(self)
if not enabled then return end
if timer:elapsed() >= nextFire then
ent:queueHook("io:FireOutput.%UID%", { output = "OnTimer" }, 0)
nextFire = timer:elapsed() + interval()
end
end )
ent:addHook("io:Input.%UID%", function( payload )
local input = payload.input
if input == "Enable" then
enabled = true
nextFire = timer:elapsed() + refireTime
elseif input == "Disable" then
enabled = false
elseif input == "Toggle" then
enabled = not enabled
if enabled then
nextFire = timer:elapsed() + refireTime
end
elseif input == "Reset" then
nextFire = timer:elapsed() + refireTime
end
end)

View File

@ -0,0 +1,95 @@
local ent = ent
local metadata = ent:getComponent("Metadata")
local metadataValve = metadata["valve"] or {}
local SOURCE_TO_METERS = 0.07
local transform = ent:getComponent("Transform")
local physicsBody = ent:getComponent("PhysicsBody")
local speed = (tonumber(metadataValve["speed"]) or 200.0) * SOURCE_TO_METERS
local targetName = metadataValve["target"]
-- collect waypoints from path_corner entities chained via their target keyvalue
local waypoints = {}
local hasLoop = false
if type(targetName) == "string" and targetName ~= "" then
local cornerByName = {}
for i, e in ipairs(entities.all()) do
if e:name() == "path_corner" then
local meta = e:getComponent("Metadata")
local valve = meta["valve"] or {}
local tname = valve["targetname"]
if tname then
cornerByName[tname] = e
end
end
end
local current = cornerByName[targetName]
local firstUid = nil
local visited = {}
while current and not visited[current:uid()] do
visited[current:uid()] = true
if firstUid == nil then firstUid = current:uid() end
table.insert(waypoints, current:getComponent("Transform").position)
local meta = current:getComponent("Metadata")
local valve = meta["valve"] or {}
current = cornerByName[valve["target"]]
end
hasLoop = (current ~= nil and current:uid() == firstUid)
end
-- 0 = idle, 1 = moving
local state = (#waypoints >= 2) and 1 or 0
local waypointIndex = 1
local lastPosition = transform.position
ent:bind( "tick", function(self)
if state ~= 1 then return end
local target = waypoints[waypointIndex]
local toTarget = target - transform.position
local dist = math.sqrt(toTarget.x * toTarget.x + toTarget.y * toTarget.y + toTarget.z * toTarget.z)
local step = time.delta() * speed
if dist <= step then
transform.position = target
waypointIndex = waypointIndex + 1
if waypointIndex > #waypoints then
if hasLoop then
waypointIndex = 1
else
state = 0
end
end
else
local dir = toTarget / dist
transform.position = transform.position + dir * step
end
-- carry passengers riding on the train
local delta = transform.position - lastPosition
lastPosition = transform.position
if physicsBody:initialized() then
local box = physicsBody:bounds()
for i, e in ipairs(entities.all()) do
if e:uid() ~= ent:uid() then
local body = e:getComponent("PhysicsBody")
if body:initialized() then
local pos = body:getTransform().position
local riding =
pos.x >= box.min.x - 0.6 and pos.x <= box.max.x + 0.6 and
pos.z >= box.min.z - 0.6 and pos.z <= box.max.z + 0.6 and
pos.y >= box.min.y - 0.5 and pos.y <= box.max.y + 2.5
if riding then
local t = e:getComponent("Transform")
t.position = t.position + delta
end
end
end
end
end
end )

View File

@ -125,6 +125,14 @@ namespace ext {
uf::stl::unordered_map<VkCommandBuffer, pod::Checkpoint*> checkpoints; uf::stl::unordered_map<VkCommandBuffer, pod::Checkpoint*> checkpoints;
// queue API calls (submit/waitIdle/present/checkpoint reads) must not overlap on the same VkQueue, even from different threads
struct QueueLocks {
std::mutex mutex;
uf::stl::unordered_map<VkQueue, std::unique_ptr<std::mutex>> locks;
uf::stl::unordered_map<VkCommandPool, std::unique_ptr<std::mutex>> poolLocks;
};
std::unique_ptr<QueueLocks> queueLocks;
uf::Window* window; uf::Window* window;
struct QueueFamilyIndices { struct QueueFamilyIndices {
@ -176,6 +184,8 @@ namespace ext {
VkCommandPool getCommandPool( QueueEnum ); VkCommandPool getCommandPool( QueueEnum );
VkQueue getQueue( QueueEnum, uf::thread::id_t ); VkQueue getQueue( QueueEnum, uf::thread::id_t );
VkCommandPool getCommandPool( QueueEnum, uf::thread::id_t ); VkCommandPool getCommandPool( QueueEnum, uf::thread::id_t );
std::unique_lock<std::mutex> lockQueue( VkQueue queue );
std::unique_lock<std::mutex> lockPool( VkCommandPool pool );
// RAII // RAII
void initialize(); void initialize();

View File

@ -484,6 +484,7 @@ void ext::VoxelizerSceneBehavior::destroy( uf::Object& self ){
texture.device = &uf::renderer::device; texture.device = &uf::renderer::device;
texture.view = view; texture.view = view;
} }
metadata.views.clear();
} }
void ext::VoxelizerSceneBehavior::Metadata::serialize( uf::Object& self, uf::Serializer& serializer ) { void ext::VoxelizerSceneBehavior::Metadata::serialize( uf::Object& self, uf::Serializer& serializer ) {
serializer["vxgi"]["size"] = /*this->*/voxelSize.x; serializer["vxgi"]["size"] = /*this->*/voxelSize.x;

View File

@ -1422,6 +1422,52 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent )
node.metadata["holdable"] = (mass <= 35.0f) && !motionDisabled && !preventPickup; node.metadata["holdable"] = (mass <= 35.0f) && !motionDisabled && !preventPickup;
} }
// bind button
else if ( node.name == "func_button" ) {
loadJson["assets"].emplace_back("ent://scripts/valve/button.lua");
// signal to assign a physics body
if ( ext::json::isNull( node.metadata["physics"] ) ) {
node.metadata["physics"]["type"] = "bounding box";
}
}
// bind train / moving platform
else if ( node.name == "func_train" || node.name == "func_track" ) {
loadJson["assets"].emplace_back("ent://scripts/valve/train.lua");
// signal to assign a physics body
if ( ext::json::isNull( node.metadata["physics"] ) ) {
node.metadata["physics"]["type"] = "bounding box";
}
}
// bind breakable
else if ( node.name == "func_breakable" || node.name.starts_with("breakable_") ) {
loadJson["assets"].emplace_back("ent://scripts/valve/breakable.lua");
// signal to assign a physics body
if ( ext::json::isNull( node.metadata["physics"] ) ) {
node.metadata["physics"]["type"] = "mesh";
node.metadata["physics"]["category"] = "static";
}
}
// bind ladder
else if ( node.name == "func_ladder" ) {
loadJson["assets"].emplace_back("ent://scripts/valve/ladder.lua");
// signal to assign a physics body
if ( ext::json::isNull( node.metadata["physics"] ) ) {
node.metadata["physics"]["type"] = "bounding box";
node.metadata["physics"]["category"] = "trigger";
}
}
// bind logic entities (pure I/O, no physics body)
else if ( node.name.starts_with("logic_") ) {
if ( node.name == "logic_timer" ) {
loadJson["assets"].emplace_back("ent://scripts/valve/timer.lua");
} else if ( node.name == "logic_relay" ) {
loadJson["assets"].emplace_back("ent://scripts/valve/relay.lua");
} else if ( node.name == "logic_counter" ) {
loadJson["assets"].emplace_back("ent://scripts/valve/counter.lua");
} else if ( node.name == "logic_case" ) {
loadJson["assets"].emplace_back("ent://scripts/valve/case.lua");
}
}
// assume all other funcs are to have a physics body // assume all other funcs are to have a physics body
else if ( node.name.starts_with("func_") ) { else if ( node.name.starts_with("func_") ) {
if ( ext::json::isNull( node.metadata["physics"] ) ) { if ( ext::json::isNull( node.metadata["physics"] ) ) {
@ -1433,7 +1479,11 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent )
// check if trigger // check if trigger
if ( node.name.starts_with("trigger_") ) { if ( node.name.starts_with("trigger_") ) {
loadJson["assets"].emplace_back("ent://scripts/valve/trigger.lua"); if ( node.name == "trigger_hurt" ) {
loadJson["assets"].emplace_back("ent://scripts/valve/hurt.lua");
} else {
loadJson["assets"].emplace_back("ent://scripts/valve/trigger.lua");
}
// signal to assign a physics body // signal to assign a physics body
if ( ext::json::isNull( node.metadata["physics"] ) ) { if ( ext::json::isNull( node.metadata["physics"] ) ) {
node.metadata["physics"]["type"] = "bounding box"; node.metadata["physics"]["type"] = "bounding box";

View File

@ -39,7 +39,7 @@ void ext::vulkan::Buffer::aliasBuffer( const ext::vulkan::Buffer& buffer ) {
this->memory = buffer.memory; this->memory = buffer.memory;
this->descriptor = buffer.descriptor; this->descriptor = buffer.descriptor;
this->alignment = buffer.alignment; this->alignment = buffer.alignment;
this->stride = buffer.alignment; this->stride = buffer.stride;
this->address = buffer.address; this->address = buffer.address;
this->mapped = buffer.mapped; this->mapped = buffer.mapped;
this->usage = buffer.usage; this->usage = buffer.usage;

View File

@ -617,10 +617,14 @@ VkCommandBuffer ext::vulkan::Device::createCommandBuffer( VkCommandBufferLevel l
if ( !pool.empty() ) { if ( !pool.empty() ) {
commandBuffer = pool.top(); commandBuffer = pool.top();
pool.pop(); pool.pop();
// pooled command buffers are returned in the recorded state, they must be reset before re-recording
vkResetCommandBuffer( commandBuffer, 0 );
} }
} }
if ( commandBuffer == VK_NULL_HANDLE ) { if ( commandBuffer == VK_NULL_HANDLE ) {
VkCommandBufferAllocateInfo cmdBufAllocateInfo = ext::vulkan::initializers::commandBufferAllocateInfo( getCommandPool(queue), level, 1 ); auto pool = getCommandPool( queue );
auto lock = this->lockPool( pool );
VkCommandBufferAllocateInfo cmdBufAllocateInfo = ext::vulkan::initializers::commandBufferAllocateInfo( pool, level, 1 );
VK_CHECK_RESULT( vkAllocateCommandBuffers( logicalDevice, &cmdBufAllocateInfo, &commandBuffer ) ); VK_CHECK_RESULT( vkAllocateCommandBuffers( logicalDevice, &cmdBufAllocateInfo, &commandBuffer ) );
} }
@ -647,25 +651,28 @@ void ext::vulkan::Device::flushCommandBuffer( VkCommandBuffer commandBuffer, Que
submitInfo.pCommandBuffers = &commandBuffer; submitInfo.pCommandBuffers = &commandBuffer;
auto queue = getQueue( queueType ); auto queue = getQueue( queueType );
VK_CHECK_RESULT(vkQueueSubmit( queue, 1, &submitInfo, fence)); {
auto lock = this->lockQueue( queue );
VK_CHECK_RESULT(vkQueueSubmit( queue, 1, &submitInfo, fence));
if ( immediate ) { if ( immediate ) {
VkResult res = vkWaitForFences( this->logicalDevice, 1, &fence, VK_TRUE, VK_DEFAULT_FENCE_TIMEOUT ); VkResult res = vkWaitForFences( this->logicalDevice, 1, &fence, VK_TRUE, VK_DEFAULT_FENCE_TIMEOUT );
VK_CHECK_QUEUE_CHECKPOINT( queue, res ); VK_CHECK_QUEUE_CHECKPOINT( queue, res );
uf::checkpoint::deallocate(checkpoints[commandBuffer]); uf::checkpoint::deallocate(checkpoints[commandBuffer]);
checkpoints[commandBuffer] = NULL; checkpoints[commandBuffer] = NULL;
checkpoints.erase(commandBuffer); checkpoints.erase(commandBuffer);
this->destroyFence( fence ); this->destroyFence( fence );
this->reusable.commandBuffers[queueType][uf::thread::current_id()].emplace( commandBuffer ); this->reusable.commandBuffers[queueType][uf::thread::current_id()].emplace( commandBuffer );
} else { } else {
ext::vulkan::mutex.lock(); ext::vulkan::mutex.lock();
auto& transient = this->transient.commandBuffers[queueType][uf::thread::current_id()]; auto& transient = this->transient.commandBuffers[queueType][uf::thread::current_id()];
transient.commandBuffers.emplace_back(commandBuffer); transient.commandBuffers.emplace_back(commandBuffer);
transient.fences.emplace_back(fence); transient.fences.emplace_back(fence);
ext::vulkan::mutex.unlock(); ext::vulkan::mutex.unlock();
}
} }
} }
pod::Checkpoint* ext::vulkan::Device::markCommandBuffer( VkCommandBuffer commandBuffer, pod::Checkpoint::Type type, const uf::stl::string& name, const uf::stl::string& info ) { pod::Checkpoint* ext::vulkan::Device::markCommandBuffer( VkCommandBuffer commandBuffer, pod::Checkpoint::Type type, const uf::stl::string& name, const uf::stl::string& info ) {
@ -703,25 +710,28 @@ void ext::vulkan::Device::flushCommandBuffer( ext::vulkan::CommandBuffer& comman
submitInfo.pCommandBuffers = &commandBuffer.handle; submitInfo.pCommandBuffers = &commandBuffer.handle;
auto queue = getQueue( commandBuffer.queueType, commandBuffer.threadId ); auto queue = getQueue( commandBuffer.queueType, commandBuffer.threadId );
VK_CHECK_RESULT(vkQueueSubmit( queue, 1, &submitInfo, fence)); {
auto lock = this->lockQueue( queue );
VK_CHECK_RESULT(vkQueueSubmit( queue, 1, &submitInfo, fence));
if ( commandBuffer.immediate ) { if ( commandBuffer.immediate ) {
VkResult res = vkWaitForFences( this->logicalDevice, 1, &fence, VK_TRUE, VK_DEFAULT_FENCE_TIMEOUT ); VkResult res = vkWaitForFences( this->logicalDevice, 1, &fence, VK_TRUE, VK_DEFAULT_FENCE_TIMEOUT );
VK_CHECK_QUEUE_CHECKPOINT( queue, res ); VK_CHECK_QUEUE_CHECKPOINT( queue, res );
uf::checkpoint::deallocate(checkpoints[commandBuffer.handle]); uf::checkpoint::deallocate(checkpoints[commandBuffer.handle]);
checkpoints[commandBuffer.handle] = NULL; checkpoints[commandBuffer.handle] = NULL;
checkpoints.erase(commandBuffer.handle); checkpoints.erase(commandBuffer.handle);
this->destroyFence( fence ); this->destroyFence( fence );
this->reusable.commandBuffers[commandBuffer.queueType][commandBuffer.threadId].emplace( commandBuffer.handle ); this->reusable.commandBuffers[commandBuffer.queueType][commandBuffer.threadId].emplace( commandBuffer.handle );
} else { } else {
ext::vulkan::mutex.lock(); ext::vulkan::mutex.lock();
auto& transient = this->transient.commandBuffers[commandBuffer.queueType][commandBuffer.threadId]; auto& transient = this->transient.commandBuffers[commandBuffer.queueType][commandBuffer.threadId];
transient.commandBuffers.emplace_back(commandBuffer.handle); transient.commandBuffers.emplace_back(commandBuffer.handle);
transient.fences.emplace_back(fence); transient.fences.emplace_back(fence);
ext::vulkan::mutex.unlock(); ext::vulkan::mutex.unlock();
}
} }
} }
@ -889,10 +899,28 @@ VkQueue ext::vulkan::Device::getQueue( ext::vulkan::QueueEnum queueEnum, uf::thr
} }
return queue; return queue;
} }
std::unique_lock<std::mutex> ext::vulkan::Device::lockQueue( VkQueue queue ) {
UF_ASSERT( this->queueLocks );
std::lock_guard<std::mutex> guard( this->queueLocks->mutex );
auto& locks = this->queueLocks->locks;
auto it = locks.find( queue );
if ( it == locks.end() ) it = locks.emplace( queue, std::make_unique<std::mutex>() ).first;
return std::unique_lock<std::mutex>( *it->second );
}
std::unique_lock<std::mutex> ext::vulkan::Device::lockPool( VkCommandPool pool ) {
UF_ASSERT( this->queueLocks );
std::lock_guard<std::mutex> guard( this->queueLocks->mutex );
auto& locks = this->queueLocks->poolLocks;
auto it = locks.find( pool );
if ( it == locks.end() ) it = locks.emplace( pool, std::make_unique<std::mutex>() ).first;
return std::unique_lock<std::mutex>( *it->second );
}
void ext::vulkan::Device::initialize() { void ext::vulkan::Device::initialize() {
auto& device = *this; auto& device = *this;
this->queueLocks = std::make_unique<QueueLocks>();
uf::stl::vector<uf::stl::string> instanceLayers = { uf::stl::vector<uf::stl::string> instanceLayers = {
// "VK_LAYER_KHRONOS_synchronization2", // "VK_LAYER_KHRONOS_synchronization2",
}; };
@ -1172,6 +1200,41 @@ void ext::vulkan::Device::initialize() {
// Else we use the same queue // Else we use the same queue
queueFamilyIndices.transfer = queueFamilyIndices.graphics; queueFamilyIndices.transfer = queueFamilyIndices.graphics;
} }
// Present queue (must be resolved before tallying, queues are created per-family)
{
uint32_t graphicsQueueNodeIndex = UINT32_MAX;
uint32_t presentQueueNodeIndex = UINT32_MAX;
uint32_t computeQueueNodeIndex = UINT32_MAX;
uint32_t transferQueueNodeIndex = UINT32_MAX;
int i = 0;
for (const auto& queueFamily : queueFamilyProperties) {
if ( queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT ) {
graphicsQueueNodeIndex = i;
}
if ( queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_COMPUTE_BIT ) {
computeQueueNodeIndex = i;
}
if ( queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_TRANSFER_BIT ) {
transferQueueNodeIndex = i;
}
VkBool32 presentSupport = false;
vkGetPhysicalDeviceSurfaceSupportKHR( this->physicalDevice, i, surface, &presentSupport );
if ( queueFamily.queueCount > 0 && presentSupport ) {
presentQueueNodeIndex = i;
}
if ( graphicsQueueNodeIndex != UINT32_MAX && presentQueueNodeIndex != UINT32_MAX && computeQueueNodeIndex != UINT32_MAX ) break;
i++;
}
if ( presentQueueNodeIndex == UINT32_MAX ) presentQueueNodeIndex = queueFamilyIndices.graphics;
queueFamilyIndices.present = presentQueueNodeIndex;
}
// Dedicated acquire queue // Dedicated acquire queue
{ {
queueFamilyIndices.acquire = queueFamilyIndices.present; queueFamilyIndices.acquire = queueFamilyIndices.present;
@ -1203,7 +1266,12 @@ void ext::vulkan::Device::initialize() {
{ {
std::map<uint32_t, uint32_t> familyIndexCounters; std::map<uint32_t, uint32_t> familyIndexCounters;
auto assignQueueIndex = [&](uint32_t family) -> uint32_t { auto assignQueueIndex = [&](uint32_t family) -> uint32_t {
return familyIndexCounters[family]++; // roles may share a queue when the family has fewer queues than requested
uint32_t available = MAX( 1, std::min( requestedQueuesPerFamily[family], queueFamilyProperties[family].queueCount ) );
auto& counter = familyIndexCounters[family];
uint32_t index = counter % available;
counter++;
return index;
}; };
device.queueIndices.graphics = assignQueueIndex( device.queueFamilyIndices.graphics ); device.queueIndices.graphics = assignQueueIndex( device.queueFamilyIndices.graphics );
@ -1227,8 +1295,7 @@ void ext::vulkan::Device::initialize() {
deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
deviceCreateInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());; deviceCreateInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());;
deviceCreateInfo.pQueueCreateInfos = queueCreateInfos.data(); deviceCreateInfo.pQueueCreateInfos = queueCreateInfos.data();
// deviceCreateInfo.pEnabledFeatures = &enabledFeatures; deviceCreateInfo.pEnabledFeatures = ext::vulkan::settings::requested::featureChain["physicalDevice2"].as<bool>(false) ? nullptr : &enabledFeatures;
deviceCreateInfo.pEnabledFeatures = nullptr;
VkDeviceGroupDeviceCreateInfo groupDeviceCreateInfo = {}; VkDeviceGroupDeviceCreateInfo groupDeviceCreateInfo = {};
groupDeviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_GROUP_DEVICE_CREATE_INFO; groupDeviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_GROUP_DEVICE_CREATE_INFO;
@ -1409,43 +1476,12 @@ void ext::vulkan::Device::initialize() {
getCommandPool( QueueEnum::TRANSFER ); getCommandPool( QueueEnum::TRANSFER );
// Set queue // Set queue
{ {
uint32_t graphicsQueueNodeIndex = UINT32_MAX;
uint32_t presentQueueNodeIndex = UINT32_MAX;
uint32_t computeQueueNodeIndex = UINT32_MAX;
uint32_t transferQueueNodeIndex = UINT32_MAX;
int i = 0;
for (const auto& queueFamily : queueFamilyProperties) {
if ( queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT ) {
graphicsQueueNodeIndex = i;
}
if ( queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_COMPUTE_BIT ) {
computeQueueNodeIndex = i;
}
if ( queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_TRANSFER_BIT ) {
transferQueueNodeIndex = i;
}
VkBool32 presentSupport = false;
vkGetPhysicalDeviceSurfaceSupportKHR( this->physicalDevice, i, surface, &presentSupport );
if ( queueFamily.queueCount > 0 && presentSupport ) {
presentQueueNodeIndex = i;
}
if ( graphicsQueueNodeIndex != UINT32_MAX && presentQueueNodeIndex != UINT32_MAX && computeQueueNodeIndex != UINT32_MAX ) break;
i++;
}
VK_VALIDATION_MESSAGE("Graphics queue: family={}, index={}", device.queueFamilyIndices.graphics, device.queueIndices.graphics ); VK_VALIDATION_MESSAGE("Graphics queue: family={}, index={}", device.queueFamilyIndices.graphics, device.queueIndices.graphics );
VK_VALIDATION_MESSAGE("Compute queue: family={}, index={}", device.queueFamilyIndices.compute, device.queueIndices.compute ); VK_VALIDATION_MESSAGE("Compute queue: family={}, index={}", device.queueFamilyIndices.compute, device.queueIndices.compute );
VK_VALIDATION_MESSAGE("Transfer queue: family={}, index={}", device.queueFamilyIndices.transfer, device.queueIndices.transfer ); VK_VALIDATION_MESSAGE("Transfer queue: family={}, index={}", device.queueFamilyIndices.transfer, device.queueIndices.transfer );
VK_VALIDATION_MESSAGE("Present queue: family={}, index={}", device.queueFamilyIndices.present, device.queueIndices.present ); VK_VALIDATION_MESSAGE("Present queue: family={}, index={}", device.queueFamilyIndices.present, device.queueIndices.present );
VK_VALIDATION_MESSAGE("Acquire queue: family={}, index={}", device.queueFamilyIndices.acquire, device.queueIndices.acquire ); VK_VALIDATION_MESSAGE("Acquire queue: family={}, index={}", device.queueFamilyIndices.acquire, device.queueIndices.acquire );
device.queueFamilyIndices.present = presentQueueNodeIndex;
getQueue( QueueEnum::GRAPHICS ); getQueue( QueueEnum::GRAPHICS );
getQueue( QueueEnum::PRESENT ); getQueue( QueueEnum::PRESENT );
getQueue( QueueEnum::COMPUTE ); getQueue( QueueEnum::COMPUTE );
@ -1613,7 +1649,7 @@ void ext::vulkan::Device::destroy() {
for ( auto& pair_1 : this->transient.commandBuffers ) { for ( auto& pair_1 : this->transient.commandBuffers ) {
for ( auto& pair : pair_1.second ) { for ( auto& pair : pair_1.second ) {
for ( auto& commandBuffer : pair.second.commandBuffers ) { for ( auto& commandBuffer : pair.second.commandBuffers ) {
vkFreeCommandBuffers(logicalDevice, getCommandPool( pair_1.first ), 1, &commandBuffer); vkFreeCommandBuffers(logicalDevice, getCommandPool( pair_1.first, pair.first ), 1, &commandBuffer);
VK_UNREGISTER_HANDLE( commandBuffer ); VK_UNREGISTER_HANDLE( commandBuffer );
commandBuffer = VK_NULL_HANDLE; commandBuffer = VK_NULL_HANDLE;
} }
@ -1635,8 +1671,8 @@ void ext::vulkan::Device::destroy() {
VK_UNREGISTER_HANDLE( fence ); VK_UNREGISTER_HANDLE( fence );
} }
for ( auto& [queueType, threadMap] : this->reusable.commandBuffers) { for ( auto& [queueType, threadMap] : this->reusable.commandBuffers) {
VkCommandPool commandPool = getCommandPool(queueType);
for ( auto& [threadId, pool] : threadMap) { for ( auto& [threadId, pool] : threadMap) {
VkCommandPool commandPool = getCommandPool(queueType, threadId);
if ( pool.empty() ) continue; if ( pool.empty() ) continue;
uf::stl::vector<VkCommandBuffer> buffersToFree; uf::stl::vector<VkCommandBuffer> buffersToFree;
buffersToFree.reserve(pool.size()); buffersToFree.reserve(pool.size());
@ -1670,6 +1706,18 @@ void ext::vulkan::Device::destroy() {
for ( auto& pair : Pipeline::pipelines ) pair.second.destroy(); for ( auto& pair : Pipeline::pipelines ) pair.second.destroy();
Pipeline::pipelines.clear(); Pipeline::pipelines.clear();
// pipelines may have deferred their SBT buffers into transient.buffers above
for ( auto& buffer : this->transient.buffers ) {
buffer.destroy(false);
}
this->transient.buffers.clear();
// deferred texture destroys (e.g. the empty textures) that outlived the last flush
for ( auto& texture : this->transient.textures ) {
texture.destroy(false);
}
this->transient.textures.clear();
descriptorAllocator.destroy(); descriptorAllocator.destroy();
for ( auto& pair : this->commandPool.graphics.container() ) { for ( auto& pair : this->commandPool.graphics.container() ) {
@ -1687,6 +1735,20 @@ void ext::vulkan::Device::destroy() {
VK_UNREGISTER_HANDLE( pair.second ); VK_UNREGISTER_HANDLE( pair.second );
pair.second = VK_NULL_HANDLE; pair.second = VK_NULL_HANDLE;
} }
// the VMA allocator must die while the device is still alive, and only once every allocation it owns has been freed
if ( allocator ) {
VmaTotalStatistics statistics = {};
vmaCalculateStatistics( allocator, &statistics );
uint32_t allocationCount = 0;
for ( auto& detail : statistics.memoryType ) allocationCount += detail.statistics.allocationCount;
if ( allocationCount > 0 ) {
UF_MSG_DEBUG("VMA allocator still owns {} allocations at shutdown", allocationCount );
}
vmaDestroyAllocator( allocator );
VK_UNREGISTER_HANDLE( allocator );
allocator = nullptr;
}
if ( this->logicalDevice ) { if ( this->logicalDevice ) {
vkDestroyDevice( this->logicalDevice, nullptr ); vkDestroyDevice( this->logicalDevice, nullptr );
VK_UNREGISTER_HANDLE( this->logicalDevice ); VK_UNREGISTER_HANDLE( this->logicalDevice );
@ -1706,9 +1768,6 @@ void ext::vulkan::Device::destroy() {
VK_UNREGISTER_HANDLE( this->instance ); VK_UNREGISTER_HANDLE( this->instance );
this->instance = nullptr; this->instance = nullptr;
} }
// vmaDestroyAllocator( allocator );
VK_UNREGISTER_HANDLE( allocator );
} }
void ext::vulkan::DescriptorAllocator::initialize(VkDevice inDevice) { void ext::vulkan::DescriptorAllocator::initialize(VkDevice inDevice) {

View File

@ -487,16 +487,16 @@ void ext::vulkan::Pipeline::destroy() {
descriptorPool = VK_NULL_HANDLE; descriptorPool = VK_NULL_HANDLE;
} }
*/ */
if ( pipelineLayout != VK_NULL_HANDLE ) {
vkDestroyPipelineLayout( *device, pipelineLayout, nullptr );
VK_UNREGISTER_HANDLE( pipelineLayout );
pipelineLayout = VK_NULL_HANDLE;
}
if ( pipeline != VK_NULL_HANDLE ) { if ( pipeline != VK_NULL_HANDLE ) {
vkDestroyPipeline( *device, pipeline, nullptr ); vkDestroyPipeline( *device, pipeline, nullptr );
VK_UNREGISTER_HANDLE( pipeline ); VK_UNREGISTER_HANDLE( pipeline );
pipeline = VK_NULL_HANDLE; pipeline = VK_NULL_HANDLE;
} }
if ( pipelineLayout != VK_NULL_HANDLE ) {
vkDestroyPipelineLayout( *device, pipelineLayout, nullptr );
VK_UNREGISTER_HANDLE( pipelineLayout );
pipelineLayout = VK_NULL_HANDLE;
}
for ( auto descriptorSetLayout : descriptorSetLayouts ) { for ( auto descriptorSetLayout : descriptorSetLayouts ) {
if ( descriptorSetLayout != VK_NULL_HANDLE ) { if ( descriptorSetLayout != VK_NULL_HANDLE ) {
vkDestroyDescriptorSetLayout( *device, descriptorSetLayout, nullptr ); vkDestroyDescriptorSetLayout( *device, descriptorSetLayout, nullptr );
@ -505,6 +505,8 @@ void ext::vulkan::Pipeline::destroy() {
} }
descriptorSetLayouts.clear(); descriptorSetLayouts.clear();
ext::vulkan::Buffers::destroy();
// if ( settings::experimental::dedicatedThread ) ext::vulkan::states::rebuild = true; // if ( settings::experimental::dedicatedThread ) ext::vulkan::states::rebuild = true;
/* /*
if ( ext::vulkan::hasRenderMode(descriptor.renderMode, true) ) { if ( ext::vulkan::hasRenderMode(descriptor.renderMode, true) ) {
@ -1004,6 +1006,8 @@ void ext::vulkan::DescriptorSets::update( const Graphic& graphic, const GraphicD
} else */ { } else */ {
vkUpdateDescriptorSets( *device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL ); vkUpdateDescriptorSets( *device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL );
} }
// the updated sets may be bound into standing command buffers; re-record them so they pick up the new contents
renderMode.rerecord = true;
this->metadata.built = true; this->metadata.built = true;
return; return;
@ -1714,6 +1718,10 @@ void ext::vulkan::Graphic::generateTopAccelerationStructure( const uf::stl::vect
} else UF_EXCEPTION("Buffers not found: {}", "tlasInstance"); } else UF_EXCEPTION("Buffers not found: {}", "tlasInstance");
auto& buffer = this->buffers.at(instanceIndex); auto& buffer = this->buffers.at(instanceIndex);
if ( instancesVK.size() * sizeof( VkAccelerationStructureInstanceKHR ) > buffer.allocationInfo.size ) {
buffer.destroy();
buffer.initialize( NULL, instancesVK.size() * sizeof( VkAccelerationStructureInstanceKHR ), VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR );
}
void* map = buffer.map(); void* map = buffer.map();
uf::stl::memcpy(map, instancesVK.data(), instancesVK.size() * sizeof(VkAccelerationStructureInstanceKHR)); uf::stl::memcpy(map, instancesVK.data(), instancesVK.size() * sizeof(VkAccelerationStructureInstanceKHR));
buffer.unmap(); buffer.unmap();
@ -1942,7 +1950,7 @@ void ext::vulkan::Graphic::initializeDescriptorSet() {
initializeDescriptorSet( this->descriptor ); initializeDescriptorSet( this->descriptor );
} }
ext::vulkan::DescriptorSets& ext::vulkan::Graphic::initializeDescriptorSet( const GraphicDescriptor& descriptor ) { ext::vulkan::DescriptorSets& ext::vulkan::Graphic::initializeDescriptorSet( const GraphicDescriptor& descriptor ) {
auto& pipeline = getPipeline(); auto& pipeline = getPipeline( descriptor );
auto& descriptorSet = descriptorSets[descriptor]; auto& descriptorSet = descriptorSets[descriptor];
// ensure pipeline exists (because we're passing this as const) // ensure pipeline exists (because we're passing this as const)

View File

@ -267,13 +267,17 @@ ext::vulkan::RenderMode::commands_container_t& ext::vulkan::RenderMode::getComma
if ( !exists ) { if ( !exists ) {
commands.resize( swapchain.buffers ); commands.resize( swapchain.buffers );
VkCommandBufferAllocateInfo cmdBufAllocateInfo = ext::vulkan::initializers::commandBufferAllocateInfo( auto pool = device->getCommandPool(this->queueEnum, id);
device->getCommandPool(this->queueEnum, id), {
VK_COMMAND_BUFFER_LEVEL_PRIMARY, auto lock = device->lockPool( pool );
static_cast<uint32_t>(commands.size()) VkCommandBufferAllocateInfo cmdBufAllocateInfo = ext::vulkan::initializers::commandBufferAllocateInfo(
); pool,
VK_COMMAND_BUFFER_LEVEL_PRIMARY,
static_cast<uint32_t>(commands.size())
);
VK_CHECK_RESULT(vkAllocateCommandBuffers(*device, &cmdBufAllocateInfo, commands.data())); VK_CHECK_RESULT(vkAllocateCommandBuffers(*device, &cmdBufAllocateInfo, commands.data()));
}
} }
return commands; return commands;
} }
@ -295,7 +299,10 @@ void ext::vulkan::RenderMode::cleanupAllCommands() {
if ( commandBuffers.empty() ) continue; if ( commandBuffers.empty() ) continue;
VkQueue queue = device->getQueue( queueEnum, threadID ); VkQueue queue = device->getQueue( queueEnum, threadID );
vkQueueWaitIdle( queue ); {
auto lock = device->lockQueue( queue );
vkQueueWaitIdle( queue );
}
/* /*
VkResult res = vkWaitForFences( *device, fences.size(), fences.data(), VK_TRUE, VK_DEFAULT_FENCE_TIMEOUT ); VkResult res = vkWaitForFences( *device, fences.size(), fences.data(), VK_TRUE, VK_DEFAULT_FENCE_TIMEOUT );
VK_CHECK_QUEUE_CHECKPOINT( queue, res ); VK_CHECK_QUEUE_CHECKPOINT( queue, res );
@ -307,7 +314,11 @@ void ext::vulkan::RenderMode::cleanupAllCommands() {
device->checkpoints.erase(commandBuffer); device->checkpoints.erase(commandBuffer);
} }
vkFreeCommandBuffers( *device, device->getCommandPool(queueEnum, threadID), static_cast<uint32_t>(commandBuffers.size()), commandBuffers.data()); {
auto pool = device->getCommandPool(queueEnum, threadID);
auto lock = device->lockPool( pool );
vkFreeCommandBuffers( *device, pool, static_cast<uint32_t>(commandBuffers.size()), commandBuffers.data());
}
commandBuffers.clear(); commandBuffers.clear();
} }
container.clear(); container.clear();
@ -320,7 +331,10 @@ void ext::vulkan::RenderMode::cleanupCommands( uf::thread::id_t id ) {
VkQueue queue = device->getQueue( queueEnum, threadID ); VkQueue queue = device->getQueue( queueEnum, threadID );
vkQueueWaitIdle( queue ); {
auto lock = device->lockQueue( queue );
vkQueueWaitIdle( queue );
}
/* /*
VkResult res = vkWaitForFences( *device, fences.size(), fences.data(), VK_TRUE, VK_DEFAULT_FENCE_TIMEOUT ); VkResult res = vkWaitForFences( *device, fences.size(), fences.data(), VK_TRUE, VK_DEFAULT_FENCE_TIMEOUT );
VK_CHECK_QUEUE_CHECKPOINT( queue, res ); VK_CHECK_QUEUE_CHECKPOINT( queue, res );
@ -332,7 +346,11 @@ void ext::vulkan::RenderMode::cleanupCommands( uf::thread::id_t id ) {
device->checkpoints.erase(commandBuffer); device->checkpoints.erase(commandBuffer);
} }
vkFreeCommandBuffers( *device, device->getCommandPool(queueEnum, threadID), static_cast<uint32_t>(commandBuffers.size()), commandBuffers.data()); {
auto pool = device->getCommandPool(queueEnum, threadID);
auto lock = device->lockPool( pool );
vkFreeCommandBuffers( *device, pool, static_cast<uint32_t>(commandBuffers.size()), commandBuffers.data());
}
commandBuffers.clear(); commandBuffers.clear();
} }
this->commands.cleanup( id ); this->commands.cleanup( id );
@ -481,7 +499,9 @@ void ext::vulkan::RenderMode::destroy() {
for ( auto& pair : this->commands.container() ) { for ( auto& pair : this->commands.container() ) {
if ( !pair.second.empty() ) { if ( !pair.second.empty() ) {
vkFreeCommandBuffers( *device, device->getCommandPool(this->queueEnum, pair.first), static_cast<uint32_t>(pair.second.size()), pair.second.data()); auto pool = device->getCommandPool(this->queueEnum, pair.first);
auto lock = device->lockPool( pool );
vkFreeCommandBuffers( *device, pool, static_cast<uint32_t>(pair.second.size()), pair.second.data());
} }
pair.second.clear(); pair.second.clear();
} }
@ -506,9 +526,12 @@ void ext::vulkan::RenderMode::synchronize( uint64_t timeout ) {
lockMutex(); lockMutex();
VkQueue queue = device->getQueue( queueEnum, this->mostRecentCommandPoolId ); VkQueue queue = device->getQueue( queueEnum, this->mostRecentCommandPoolId );
VkResult res = vkWaitForFences( *device, fences.size(), fences.data(), VK_TRUE, timeout ); {
// VkResult res = vkWaitForFences(*device, 1, &fences[states::currentBuffer], VK_TRUE, timeout); auto lock = device->lockQueue( queue );
VK_CHECK_QUEUE_CHECKPOINT( queue, res ); VkResult res = vkWaitForFences( *device, fences.size(), fences.data(), VK_TRUE, timeout );
// VkResult res = vkWaitForFences(*device, 1, &fences[states::currentBuffer], VK_TRUE, timeout);
VK_CHECK_QUEUE_CHECKPOINT( queue, res );
}
unlockMutex(); unlockMutex();
} }

View File

@ -111,7 +111,8 @@ VkSubmitInfo ext::vulkan::BaseRenderMode::queue() {
submitInfo.pWaitDstStageMask = waitStageMask; submitInfo.pWaitDstStageMask = waitStageMask;
submitInfo.pWaitSemaphores = &swapchain.presentCompleteSemaphores[states::currentBuffer]; submitInfo.pWaitSemaphores = &swapchain.presentCompleteSemaphores[states::currentBuffer];
submitInfo.waitSemaphoreCount = 1; submitInfo.waitSemaphoreCount = 1;
submitInfo.pSignalSemaphores = &renderCompleteSemaphores[states::currentBuffer]; // the present-wait semaphore is paired with the acquired image, so it is only reused once that image is re-acquired
submitInfo.pSignalSemaphores = &renderCompleteSemaphores[states::imageIndex];
submitInfo.signalSemaphoreCount = 1; submitInfo.signalSemaphoreCount = 1;
submitInfo.pCommandBuffers = &commands[states::currentBuffer]; submitInfo.pCommandBuffers = &commands[states::currentBuffer];
submitInfo.commandBufferCount = 1; submitInfo.commandBufferCount = 1;
@ -129,11 +130,16 @@ void ext::vulkan::BaseRenderMode::render() {
{ {
VkSubmitInfo submitInfo = this->queue(); VkSubmitInfo submitInfo = this->queue();
VkQueue queue = device->getQueue( QueueEnum::GRAPHICS ); VkQueue queue = device->getQueue( QueueEnum::GRAPHICS );
auto lock = device->lockQueue( queue );
VkResult res = vkQueueSubmit( queue, 1, &submitInfo, fences[states::currentBuffer]); VkResult res = vkQueueSubmit( queue, 1, &submitInfo, fences[states::currentBuffer]);
VK_CHECK_QUEUE_CHECKPOINT( queue, res ); VK_CHECK_QUEUE_CHECKPOINT( queue, res );
} }
VK_CHECK_RESULT(swapchain.queuePresent(device->getQueue( QueueEnum::PRESENT ), states::imageIndex, renderCompleteSemaphores[states::currentBuffer])); {
VkQueue queue = device->getQueue( QueueEnum::PRESENT );
auto lock = device->lockQueue( queue );
VK_CHECK_RESULT(swapchain.queuePresent( queue, states::imageIndex, renderCompleteSemaphores[states::imageIndex]));
}
states::currentBuffer = (states::currentBuffer + 1) % ext::vulkan::swapchain.buffers; states::currentBuffer = (states::currentBuffer + 1) % ext::vulkan::swapchain.buffers;
this->executed = true; this->executed = true;

View File

@ -668,10 +668,17 @@ void ext::vulkan::DeferredRenderMode::render() {
VK_COMMAND_BUFFER_CALLBACK( EXECUTE_BEGIN, VkCommandBuffer{}, 0, {} ); VK_COMMAND_BUFFER_CALLBACK( EXECUTE_BEGIN, VkCommandBuffer{}, 0, {} );
// wait on the slot's previous deferred submit so its fence can be re-signaled
VK_CHECK_RESULT(vkWaitForFences( *device, 1, &fences[states::currentBuffer], VK_TRUE, VK_DEFAULT_FENCE_TIMEOUT ));
VK_CHECK_RESULT(vkResetFences( *device, 1, &fences[states::currentBuffer] ));
VkSubmitInfo submitInfo = this->queue(); VkSubmitInfo submitInfo = this->queue();
VkQueue queue = device->getQueue( QueueEnum::GRAPHICS ); {
VkResult res = vkQueueSubmit( queue, 1, &submitInfo, VK_NULL_HANDLE/*fences[states::currentBuffer]*/); VkQueue queue = device->getQueue( QueueEnum::GRAPHICS );
VK_CHECK_QUEUE_CHECKPOINT( queue, res ); auto lock = device->lockQueue( queue );
VkResult res = vkQueueSubmit( queue, 1, &submitInfo, fences[states::currentBuffer]);
VK_CHECK_QUEUE_CHECKPOINT( queue, res );
}
VK_COMMAND_BUFFER_CALLBACK( EXECUTE_END, VkCommandBuffer{}, 0, {} ); VK_COMMAND_BUFFER_CALLBACK( EXECUTE_END, VkCommandBuffer{}, 0, {} );
@ -686,18 +693,21 @@ void ext::vulkan::DeferredRenderMode::destroy() {
::postprocesses::dof.atomicCounter.destroy(false); ::postprocesses::dof.atomicCounter.destroy(false);
for ( auto& view : ::postprocesses::bloom.views ) { for ( auto& view : ::postprocesses::bloom.views ) {
vkDestroyImageView(device->logicalDevice, view, nullptr); vkDestroyImageView(device->logicalDevice, view, nullptr);
VK_UNREGISTER_HANDLE(view); VK_UNREGISTER_HANDLE(view);
} }
::postprocesses::bloom.views.clear(); ::postprocesses::bloom.views.clear();
for ( auto& view : ::postprocesses::dof.views ) { for ( auto& view : ::postprocesses::dof.views ) {
vkDestroyImageView(device->logicalDevice, view, nullptr); vkDestroyImageView(device->logicalDevice, view, nullptr);
VK_UNREGISTER_HANDLE(view); VK_UNREGISTER_HANDLE(view);
} }
::postprocesses::dof.views.clear(); ::postprocesses::dof.views.clear();
for ( auto& view : ::postprocesses::depthPyramid.views ) { for ( auto& view : ::postprocesses::depthPyramid.views ) {
vkDestroyImageView(device->logicalDevice, view, nullptr); vkDestroyImageView(device->logicalDevice, view, nullptr);
VK_UNREGISTER_HANDLE(view); VK_UNREGISTER_HANDLE(view);
} }

View File

@ -333,8 +333,11 @@ void ext::vulkan::RenderTargetRenderMode::render() {
VkSubmitInfo submitInfo = this->queue(); VkSubmitInfo submitInfo = this->queue();
VkQueue queue = device->getQueue( QueueEnum::GRAPHICS ); VkQueue queue = device->getQueue( QueueEnum::GRAPHICS );
VkResult res = vkQueueSubmit( queue, 1, &submitInfo, /*VK_NULL_HANDLE*/fences[states::currentBuffer]); {
VK_CHECK_QUEUE_CHECKPOINT( queue, res ); auto lock = device->lockQueue( queue );
VkResult res = vkQueueSubmit( queue, 1, &submitInfo, /*VK_NULL_HANDLE*/fences[states::currentBuffer]);
VK_CHECK_QUEUE_CHECKPOINT( queue, res );
}
VK_COMMAND_BUFFER_CALLBACK( EXECUTE_END, VkCommandBuffer{}, 0, {} ); VK_COMMAND_BUFFER_CALLBACK( EXECUTE_END, VkCommandBuffer{}, 0, {} );
this->executed = true; this->executed = true;

View File

@ -251,8 +251,11 @@ void ext::vulkan::VrRenderMode::render() {
VkSubmitInfo submitInfo = this->queue(); VkSubmitInfo submitInfo = this->queue();
VkQueue queue = device->getQueue( QueueEnum::GRAPHICS ); VkQueue queue = device->getQueue( QueueEnum::GRAPHICS );
VkResult res = vkQueueSubmit( queue, 1, &submitInfo, /*VK_NULL_HANDLE*/fences[states::currentBuffer]); {
VK_CHECK_QUEUE_CHECKPOINT( queue, res ); auto lock = device->lockQueue( queue );
VkResult res = vkQueueSubmit( queue, 1, &submitInfo, /*VK_NULL_HANDLE*/fences[states::currentBuffer]);
VK_CHECK_QUEUE_CHECKPOINT( queue, res );
}
VK_COMMAND_BUFFER_CALLBACK( EXECUTE_END, VkCommandBuffer{}, 0, {} ); VK_COMMAND_BUFFER_CALLBACK( EXECUTE_END, VkCommandBuffer{}, 0, {} );
#if UF_USE_OPENVR #if UF_USE_OPENVR
ext::openvr::submit(); ext::openvr::submit();

View File

@ -35,6 +35,17 @@ size_t ext::vulkan::RenderTarget::attach( const Attachment::Descriptor& descript
VK_UNREGISTER_HANDLE( view ); VK_UNREGISTER_HANDLE( view );
} }
attachment->views.clear(); attachment->views.clear();
// view and framebufferView are distinct handles not covered by views[]
if ( attachment->view ) {
vkDestroyImageView( *device, attachment->view, nullptr );
VK_UNREGISTER_HANDLE( attachment->view );
attachment->view = VK_NULL_HANDLE;
}
if ( attachment->framebufferView ) {
vkDestroyImageView( *device, attachment->framebufferView, nullptr );
VK_UNREGISTER_HANDLE( attachment->framebufferView );
attachment->framebufferView = VK_NULL_HANDLE;
}
if ( attachment->image && attachment->descriptor.layout != VK_IMAGE_LAYOUT_PRESENT_SRC_KHR ) { if ( attachment->image && attachment->descriptor.layout != VK_IMAGE_LAYOUT_PRESENT_SRC_KHR ) {
vmaDestroyImage( allocator, attachment->image, attachment->allocation ); vmaDestroyImage( allocator, attachment->image, attachment->allocation );
attachment->image = VK_NULL_HANDLE; attachment->image = VK_NULL_HANDLE;
@ -482,7 +493,17 @@ void ext::vulkan::RenderTarget::destroy() {
framebuffers.clear(); framebuffers.clear();
for ( auto& attachment : attachments ) { for ( auto& attachment : attachments ) {
if ( attachment.descriptor.aliased ) continue; if ( attachment.descriptor.aliased ) {
if ( attachment.descriptor.layout != VK_IMAGE_LAYOUT_PRESENT_SRC_KHR ) continue;
for ( auto& view : attachment.views ) {
if ( view != VK_NULL_HANDLE ) {
vkDestroyImageView( *device, view, nullptr );
VK_UNREGISTER_HANDLE( view );
}
}
attachment.views.clear();
continue;
}
if ( attachment.framebufferView ) { if ( attachment.framebufferView ) {
vkDestroyImageView(*device, attachment.framebufferView, nullptr); vkDestroyImageView(*device, attachment.framebufferView, nullptr);
VK_UNREGISTER_HANDLE( attachment.framebufferView ); VK_UNREGISTER_HANDLE( attachment.framebufferView );

View File

@ -696,6 +696,7 @@ void ext::vulkan::Texture::asRenderTarget( Device& device, uint32_t width, uint3
viewCreateInfo.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; viewCreateInfo.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
viewCreateInfo.image = image; viewCreateInfo.image = image;
VK_CHECK_RESULT(vkCreateImageView(device, &viewCreateInfo, nullptr, &view)); VK_CHECK_RESULT(vkCreateImageView(device, &viewCreateInfo, nullptr, &view));
VK_REGISTER_HANDLE( view );
// Initialize a descriptor for later use // Initialize a descriptor for later use
this->updateDescriptors(); this->updateDescriptors();

View File

@ -540,10 +540,14 @@ void ext::vulkan::tick() {
auto tasks = uf::thread::schedule( settings::invariant::multithreadedRecording ); auto tasks = uf::thread::schedule( settings::invariant::multithreadedRecording );
for ( auto& renderMode : renderModes ) { if ( !renderMode || (renderMode->executed && !renderMode->execute) ) continue; for ( auto& renderMode : renderModes ) { if ( !renderMode || (renderMode->executed && !renderMode->execute) ) continue;
if ( ext::vulkan::states::rebuild || renderMode->rebuild ) tasks.queue([renderMode]{ if ( ext::vulkan::states::rebuild || renderMode->rebuild ) tasks.queue([renderMode]{
renderMode->synchronize();
renderMode->cleanupAllCommands();
renderMode->bindPipelines(); renderMode->bindPipelines();
renderMode->createCommandBuffers(); renderMode->createCommandBuffers();
}); });
else if ( renderMode->rerecord ) tasks.queue([renderMode]{ else if ( renderMode->rerecord ) tasks.queue([renderMode]{
renderMode->synchronize();
renderMode->cleanupAllCommands();
renderMode->createCommandBuffers(); renderMode->createCommandBuffers();
}); });
} }
@ -641,9 +645,17 @@ void ext::vulkan::render() {
VK_CHECK_RESULT(vkResetFences(device, fences.size(), fences.data())); VK_CHECK_RESULT(vkResetFences(device, fences.size(), fences.data()));
} }
if ( !submitsCompute.empty() ) if ( !submitsCompute.empty() )
VK_CHECK_RESULT(vkQueueSubmit(device.getQueue( QueueEnum::COMPUTE ), submitsCompute.size(), submitsCompute.data(), ::auxFences.compute[states::currentBuffer])); {
VkQueue queue = device.getQueue( QueueEnum::COMPUTE );
auto lock = device.lockQueue( queue );
VK_CHECK_RESULT(vkQueueSubmit(queue, submitsCompute.size(), submitsCompute.data(), ::auxFences.compute[states::currentBuffer]));
}
if ( !submitsGraphics.empty() ) if ( !submitsGraphics.empty() )
VK_CHECK_RESULT(vkQueueSubmit(device.getQueue( QueueEnum::GRAPHICS ), submitsGraphics.size(), submitsGraphics.data(), ::auxFences.graphics[states::currentBuffer])); {
VkQueue queue = device.getQueue( QueueEnum::GRAPHICS );
auto lock = device.lockQueue( queue );
VK_CHECK_RESULT(vkQueueSubmit(queue, submitsGraphics.size(), submitsGraphics.data(), ::auxFences.graphics[states::currentBuffer]));
}
} }
// submit swapchain (record + submit + present) last // submit swapchain (record + submit + present) last
@ -696,6 +708,28 @@ void ext::vulkan::render() {
// ext::vulkan::mutex.unlock(); // ext::vulkan::mutex.unlock();
// wait on any in-flight transient commands so their staging resources can be destroyed, and return their command buffers to the reusable pool
for ( auto& [ queueType, commandBuffers ] : transient.commandBuffers ) {
for ( auto& [ threadId, tuple ] : commandBuffers ) {
constexpr size_t TOTAL = 64;
for ( size_t i = 0; i < tuple.fences.size(); i += TOTAL ) {
size_t count = std::min( tuple.fences.size() - i, TOTAL );
VK_CHECK_RESULT( vkWaitForFences( device, count, &tuple.fences[i], VK_TRUE, UINT64_MAX ) );
}
for ( auto fence : tuple.fences ) device.destroyFence( fence );
tuple.fences.clear();
auto& pool = device.reusable.commandBuffers[queueType][threadId];
for ( auto commandBuffer : tuple.commandBuffers ) {
if ( auto it = device.checkpoints.find(commandBuffer); it != device.checkpoints.end() ) {
uf::checkpoint::deallocate( it->second );
device.checkpoints.erase( it );
}
pool.emplace( commandBuffer );
}
tuple.commandBuffers.clear();
}
}
// cleanup in-flight buffers // cleanup in-flight buffers
for ( auto& buffer : transient.buffers ) buffer.destroy(false); for ( auto& buffer : transient.buffers ) buffer.destroy(false);
transient.buffers.clear(); transient.buffers.clear();
@ -726,8 +760,9 @@ void ext::vulkan::destroy( bool soft ) {
for ( auto& renderMode : renderModes ) { for ( auto& renderMode : renderModes ) {
if ( !renderMode || !renderMode->device ) continue; if ( !renderMode || !renderMode->device ) continue;
renderMode->destroy(); renderMode->destroy();
delete renderMode; if ( std::find( ext::vulkan::renderModes.begin(), ext::vulkan::renderModes.end(), renderMode ) != ext::vulkan::renderModes.end() ) {
renderMode = NULL; delete renderMode;
}
} }
ext::vulkan::renderModes.clear(); ext::vulkan::renderModes.clear();