Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
chitalu committed Oct 18, 2023
2 parents a3d9674 + 8dac4c2 commit b0bcba7
Show file tree
Hide file tree
Showing 31 changed files with 3,072 additions and 445 deletions.
8 changes: 8 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
# MCUT_BUILD_TESTS [default=OFF] - Build the tests (implicit dependancy on GoogleTest)
# MCUT_BUILD_TUTORIALS [default=OFF] - Build tutorials
# MCUT_BUILD_WITH_COMPUTE_HELPER_THREADPOOL [default=ON] - Build as configurable multi-threaded library
# MCUT_BUILD_WITH_API_EVENT_LOGGING [default=OFF] - Build with logging functionality which dumps event creation and destruction notices to the console
#
# This script will define the following CMake cache variables:
#
Expand Down Expand Up @@ -117,6 +118,8 @@ option(MCUT_BUILD_WITH_COMPUTE_HELPER_THREADPOOL "Configure to build MCUT engine
if (MCUT_TOPLEVEL_PROJECT AND NOT MCUT_BUILD_TUTORIALS)
option(MCUT_BUILD_TUTORIALS "Configure to build MCUT tutorials" ON)
endif()
option(MCUT_BUILD_WITH_API_EVENT_LOGGING "Configure to build MCUT with event logging to console" OFF)

#
# machine-precision-numbers library targets
#
Expand All @@ -143,6 +146,11 @@ if (MCUT_BUILD_WITH_COMPUTE_HELPER_THREADPOOL)
list(APPEND preprocessor_defs -DMCUT_WITH_COMPUTE_HELPER_THREADPOOL=1)
endif()

if (MCUT_BUILD_WITH_API_EVENT_LOGGING)
list(APPEND preprocessor_defs -DMCUT_WITH_API_EVENT_LOGGING=1)
endif()


if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
list(APPEND compilation_flags -Wall -Wextra)
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel")
Expand Down
135 changes: 94 additions & 41 deletions include/mcut/internal/frontend.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ extern "C" void debug_message_callback_impl(
extern "C" void get_debug_message_log_impl(McContext context,
McUint32 count, McSize bufSize,
McDebugSource* sources, McDebugType* types, McDebugSeverity* severities,
McSize* lengths, McChar* messageLog, McUint32& numFetched);
McSize* lengths, McChar* messageLog, McUint32& numFetched)noexcept(false);

extern "C" void debug_message_control_impl(
McContext context,
Expand All @@ -108,11 +108,11 @@ extern "C" void bind_state_impl(
const McContext context,
McFlags stateInfo,
McSize bytes,
const McVoid* pMem);
const McVoid* pMem)noexcept(false);

extern "C" void create_user_event_impl(McEvent* event, McContext context);
extern "C" void create_user_event_impl(McEvent* event, McContext context)noexcept(false);

extern "C" void set_user_event_status_impl(McEvent event, McInt32 execution_status);
extern "C" void set_user_event_status_impl(McEvent event, McInt32 execution_status)noexcept(false);

extern "C" void get_event_info_impl(
const McEvent event,
Expand All @@ -124,7 +124,7 @@ extern "C" void get_event_info_impl(
extern "C" void set_event_callback_impl(
McEvent eventHandle,
pfn_McEvent_CALLBACK eventCallback,
McVoid* data);
McVoid* data) noexcept(false);

extern "C" void wait_for_events_impl(
uint32_t numEventsInWaitlist,
Expand Down Expand Up @@ -367,7 +367,7 @@ struct event_t {
, m_timestamp_submit(0)
, m_timestamp_start(0)
, m_timestamp_end(0)
, m_command_exec_status(MC_RESULT_MAX_ENUM)
, m_command_exec_status((McUint32)((int)(MC_RESULT_MAX_ENUM)))
, m_profiling_enabled(true)
, m_command_type(command_type)
, m_user_API_command_task_emulator(nullptr)
Expand Down Expand Up @@ -472,17 +472,19 @@ void fn_delete_cc(connected_component_t* p)
// struct defining the state of a context object
struct context_t {
private:
std::atomic<bool> m_done;
std::atomic<bool> m_done; // are we finished with the context (i.e. indicate to shutdown threadpool and freeup respective resourses)
// the work-queues associated with each API (device) thread
std::vector<thread_safe_queue<function_wrapper>> m_queues;
// Master/Manager thread(s) which are responsible for running the API calls
// Master/Manager thread(s) which are responsible for running the API calls.
// Also called "device" threads.
// When a user of MCUT calls one of the APIs (e.g. mcEnqueueDispatch) the task
// of actually executing everything related to that task will be handled by
// one Manager thread. This manager thread itself will be involved in
// computing some/all part of the respective task (think of it as the "main"
// thread insofar as the API task is concerned). Some API tasks contain code
// thread insofar as the respective API task is concerned). Some API tasks contain code
// sections that run in parallel, which is where the Manager thread will also
// submit tasks to the shared compute threadpool ("m_compute_threadpool").
// NOTE: must be declared after "thread_pool_terminate" and "work_queues"
// NOTE: must be declared after "thread_pool_terminate" and "work_queues" (to avoid MT bugs when freeing up context memory)
std::vector<std::thread> m_api_threads;
// join_threads m_joiner;

Expand All @@ -492,29 +494,44 @@ struct context_t {
std::unique_ptr<thread_pool> m_compute_threadpool;
#endif

// The state and flag variable current used to configure the next dispatch call
// The state and flag variable currently used to configure the next dispatch call
McFlags m_flags = (McFlags)0;

// as it says on the tin: "tiny" number used to enforce general position
std::atomic<McDouble> m_general_position_enforcement_constant;
// The maximum number of iterations/attempts over which to try and resolve the input meshes into general position
std::atomic<McUint32> m_max_num_perturbation_attempts;
// The state and flag variable currently used to configure the next mcGetConnectedComponentData call
// (with either MC_CONNECTED_COMPONENT_DATA_FACE or MC_CONNECTED_COMPONENT_DATA_FACE_TRIANGULATION)
// This flag is primailly useful for flipping the winding order of vertices queried for a connected component
// of type FRAGMENT-SEALED-EXTERIOR-BELOW, which will have reversed normal orientation due to MCUT internal
// handling of winding order
std::atomic<McConnectedComponentFaceWindingOrder> m_connected_component_winding_order;

// The type of intersection (between source-mesh and cut-mesh) that was detected during the last/most-recent
// dispatch call.
std::atomic<McDispatchIntersectionType> m_most_recent_dispatch_intersection_type;

// This is the "main" function of each device/API/manger thread. When a context is created and
// the internal scheduling threadpool is initialised, each (device) thread is launched with this
// function. The device thread loops indefinitely (sleeping most of the time and waking up to do
// work) until the context is destroyed.
void api_thread_main(uint32_t thread_id)
{
log_msg("[MCUT] Launch API thread " << std::this_thread::get_id() << " (" << thread_id << ")");

do {
function_wrapper task;

// We try_pop() first in case the task "producer" (API) thread
// We try_pop() first in case the task "producer" (API/device/manager) thread
// already invoked cond_var.notify_one() of "m_queues[thread_id]""
// BEFORE current thread first-entered this function.
if (!m_queues[thread_id].try_pop(task)) {
// there is no work, so thread will wait until user thread gives it some work to do.
m_queues[thread_id].wait_and_pop(task);
}

if (m_done) {
break;
if (m_done) { // are we finished?
break; // quit to free up resources.
}

task();
Expand All @@ -534,11 +551,14 @@ struct context_t {
: m_done(false)
// , m_joiner(m_api_threads)
, m_flags(flags)
, m_general_position_enforcement_constant(1e-4)
, m_max_num_perturbation_attempts(1 << 2),
, m_general_position_enforcement_constant(1e-4) // 0.0001
, m_max_num_perturbation_attempts(1 << 2), // 4
// default winding order (as determing from the normals of the input mesh faces)
m_connected_component_winding_order(McConnectedComponentFaceWindingOrder::MC_CONNECTED_COMPONENT_FACE_WINDING_ORDER_AS_GIVEN)
, m_user_handle(handle)
, dbgCallbackBitfieldSource(0)
, m_most_recent_dispatch_intersection_type(McDispatchIntersectionType::MC_DISPATCH_INTERSECTION_TYPE_MAX_ENUM)
, m_user_handle(handle), // i.e. the McContext handle that the client application uses to reference an instance of the context
// debug callback flags (all zero/unset by default). User must specify what they want via debug control function.
dbgCallbackBitfieldSource(0)
, dbgCallbackBitfieldType(0)
, dbgCallbackBitfieldSeverity(0)
{
Expand All @@ -560,7 +580,7 @@ struct context_t {
m_compute_threadpool = std::unique_ptr<thread_pool>(new thread_pool(num_compute_threads, manager_thread_count));
#endif
} catch (...) {
shutdown();
shutdown(); // free up memory shutdown device threads etc.

log_msg("[MCUT] Destroy context due to exception" << m_user_handle);
throw;
Expand All @@ -575,21 +595,22 @@ struct context_t {

void shutdown()
{
m_done = true;
m_done = true; // notify device threads that they need to finished up

std::atomic_thread_fence(std::memory_order_acq_rel);
std::atomic_thread_fence(std::memory_order_acq_rel); // just for safety, put a memory barrier to make shutdown notification is recieved (probably not needed)

#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
m_compute_threadpool.reset();
m_compute_threadpool.reset(); // wake-up and destroy the helper/worker threads
#endif
for (int i = (int)m_api_threads.size() - 1; i >= 0; --i) {
m_queues[i].disrupt_wait_for_data();
if (m_api_threads[i].joinable()) {
m_api_threads[i].join();
m_queues[i].disrupt_wait_for_data(); // wake up device thread from sleeping
if (m_api_threads[i].joinable()) { // if not already joined/shutdown
m_api_threads[i].join(); // wait for it to finish
}
}
}


// reference handle used by client application to access and modify context state
McContext m_user_handle;

// returns the flags which determine the runtime configuration of this context
Expand Down Expand Up @@ -631,6 +652,16 @@ struct context_t {
{
this->m_connected_component_winding_order.store(new_value, std::memory_order_release);
}

McDispatchIntersectionType get_most_recent_dispatch_intersection_type()const
{
return this->m_most_recent_dispatch_intersection_type.load(std::memory_order_acquire);
}

void set_most_recent_dispatch_intersection_type(McDispatchIntersectionType new_value)
{
this->m_most_recent_dispatch_intersection_type.store(new_value, std::memory_order_release);
}


#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
Expand All @@ -640,15 +671,24 @@ struct context_t {
}
#endif

/*
This function serves the purpose of scheduling an API task (i.e. an async API function call from the user) by submitting this
task into an internal work-queue that will be checked and popped-from by a device thread.
*/
template <typename FunctionType>
McEvent prepare_and_submit_API_task(McCommandType cmdType, uint32_t numEventsInWaitlist, const McEvent* pEventWaitList, FunctionType api_fn)
McEvent prepare_and_submit_API_task(
McCommandType cmdType, // the type of command that the user has submitted
uint32_t numEventsInWaitlist, // the number of (previously submitted or user) events to wait for
const McEvent* pEventWaitList, // the list/array of events to wait for _before_ executing the submitted task
FunctionType api_fn // a function (lambda/functor) encapsulating the API task to be executed asynchronously
)
{
//
// create the event object associated with the enqueued task
//

std::shared_ptr<event_t> event_ptr = std::shared_ptr<event_t>(new event_t(
reinterpret_cast<McEvent>(g_objects_counter.fetch_add(1, std::memory_order_relaxed)),
reinterpret_cast<McEvent>(g_objects_counter.fetch_add(1, std::memory_order_relaxed)), // the handle is just a lightweight integer counter
cmdType));

MCUT_ASSERT(event_ptr != nullptr);
Expand All @@ -663,27 +703,29 @@ struct context_t {

// List of events the enqueued task depends on
//
// local copy that will be captured by-value (user permitted to re-use pEventWaitList)
// local copy that will be captured by-value (client application is permitted to re-use the memory pointed to by "pEventWaitList")
const std::vector<McEvent> event_waitlist(pEventWaitList, pEventWaitList + numEventsInWaitlist);

//
// Determine which manager thread to assign the task
// Determine which manager thread to assign the task to
//

// the id of manager thread that will be assigned the current task
uint32_t responsible_thread_id = UINT32_MAX;

// for each event to wait for (before executing the current task)
for (std::vector<McEvent>::const_iterator waitlist_iter = event_waitlist.cbegin(); waitlist_iter != event_waitlist.cend(); ++waitlist_iter) {
const McEvent& parent_task_event_handle = *waitlist_iter;

// get actual event object
const std::shared_ptr<event_t> parent_task_event_ptr = g_events.find_first_if([=](std::shared_ptr<event_t> e) { return e->m_user_handle == parent_task_event_handle; });

if (parent_task_event_ptr == nullptr) {
throw std::invalid_argument("invalid event in waitlist");
if (parent_task_event_ptr == nullptr) { // not found
throw std::invalid_argument("invalid event in waitlist"); // client gave us something we don't recognise
}

const bool parent_task_is_not_finished = parent_task_event_ptr->m_finished.load() == false;

// task associated with event is still running and the event is a user-event
if (parent_task_is_not_finished && parent_task_event_ptr->m_command_type != McCommandType::MC_COMMAND_USER) {
// id of manager thread, which was assigned the parent task
responsible_thread_id = parent_task_event_ptr->m_responsible_thread_id.load();
Expand All @@ -698,17 +740,19 @@ struct context_t {
const bool have_responsible_thread = responsible_thread_id != UINT32_MAX;

if (!have_responsible_thread) {
uint32_t thread_with_empty_queue = UINT32_MAX;

for (uint32_t i = 0; i < (uint32_t)m_api_threads.size(); ++i) {
if (m_queues[(i + 1) % (uint32_t)m_api_threads.size()].empty() == true) {
thread_with_empty_queue = i;
uint32_t assignedThread = UINT32_MAX;

for (uint32_t i = 0; i < (uint32_t)m_api_threads.size(); ++i) { // assign to any thread with an empty queue
const uint32_t index = (i + 1) % (uint32_t)m_api_threads.size();
if (m_queues[index].empty()) {
assignedThread = index;
break;
}
}


if (thread_with_empty_queue != UINT32_MAX) {
responsible_thread_id = thread_with_empty_queue;
if (assignedThread != UINT32_MAX) {
responsible_thread_id = assignedThread;
} else { // all threads have work to do
responsible_thread_id = 0; // just pick thread 0
}
Expand Down Expand Up @@ -858,6 +902,15 @@ struct context_t {
}
};

extern "C" void triangulate_face(
// list of indices which define all triangles that result from the CDT
std::vector<uint32_t>&cc_face_triangulation,
const std::shared_ptr<context_t>&context_uptr,
const uint32_t cc_face_vcount,
const std::vector<vertex_descriptor_t>&cc_face_vertices,
const hmesh_t & cc,
const fd_t cc_face_iter);

// list of contexts created by client/user
extern "C" threadsafe_list<std::shared_ptr<context_t>> g_contexts;

Expand Down
7 changes: 5 additions & 2 deletions include/mcut/internal/kernel.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,12 @@ struct input_t {
bool keep_fragments_sealed_inside = false;
bool keep_fragments_sealed_outside = false;
// bool include_fragment_sealed_partial = false; // See: variable above "keep_partially_sealed_connected_components"
bool keep_fragments_sealed_inside_exhaustive = false; // TODO remove
bool keep_fragments_sealed_outside_exhaustive = false; // TODO remove
//bool keep_fragments_sealed_inside_exhaustive = false; // TODO remove
//bool keep_fragments_sealed_outside_exhaustive = false; // TODO remove
// NOTE TO SELF: if the user simply wants patches, then kernel should not have to proceed to stitching!!!

bool src_mesh_is_watertight = false;
bool cut_mesh_is_watertight = false;
};

struct output_mesh_data_maps_t {
Expand Down
2 changes: 2 additions & 0 deletions include/mcut/internal/timer.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,12 @@ class mini_timer {
~mini_timer()
{
if (m_valid) {
#ifdef MCUT_WITH_API_EVENT_LOGGING
const std::chrono::time_point<std::chrono::steady_clock> now = std::chrono::steady_clock::now();
const std::chrono::milliseconds elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_start);
unsigned long long elapsed_ = elapsed.count();
log_msg("[MCUT][PROF:" << std::this_thread::get_id() << "]: \"" << m_name << "\" ("<< elapsed_ << "ms)");
#endif // #ifdef MCUT_WITH_API_EVENT_LOGGING
}
}
void set_invalid()
Expand Down
Loading

0 comments on commit b0bcba7

Please sign in to comment.