mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-21 09:52:11 +00:00
Move many third-party components' source codes from the src folder to a new folder called deps_src. The goal is to make the code structure clearer and easier to navigate.
847 lines
33 KiB
C++
847 lines
33 KiB
C++
/**
|
|
* Copyright (c) 2021-2022 Floyd M. Chitalu.
|
|
* All rights reserved.
|
|
*
|
|
* NOTE: This file is licensed under GPL-3.0-or-later (default).
|
|
* A commercial license can be purchased from Floyd M. Chitalu.
|
|
*
|
|
* License details:
|
|
*
|
|
* (A) GNU General Public License ("GPL"); a copy of which you should have
|
|
* recieved with this file.
|
|
* - see also: <http://www.gnu.org/licenses/>
|
|
* (B) Commercial license.
|
|
* - email: floyd.m.chitalu@gmail.com
|
|
*
|
|
* The commercial license options is for users that wish to use MCUT in
|
|
* their products for comercial purposes but do not wish to release their
|
|
* software products under the GPL license.
|
|
*
|
|
* Author(s) : Floyd M. Chitalu
|
|
*/
|
|
|
|
/**
|
|
* @file mcut.h
|
|
* @author Floyd M. Chitalu
|
|
* @date 11 July 2022
|
|
*
|
|
* @brief API-function implementations.
|
|
*
|
|
* NOTE: This header file defines the pre- and post-cutting processing of mesh
|
|
* data, which includes any intermediate correctons/modifications to the user's
|
|
* input meshes like 'polygon partitioning'.
|
|
*
|
|
*/
|
|
|
|
#ifndef _FRONTEND_H_
|
|
#define _FRONTEND_H_
|
|
|
|
#include "mcut/mcut.h"
|
|
|
|
#include <future>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <chrono>
|
|
|
|
#include "mcut/internal/tpool.h"
|
|
|
|
#include "mcut/internal/kernel.h"
|
|
|
|
/*
|
|
std::invalid_argument: related to the input parameters
|
|
std::runtime_error: system runtime error e.g. out of memory
|
|
std::logic_error: a bug caught through an assertion failure
|
|
std::exception: unknown error source e.g. probably another bug
|
|
*/
|
|
#define CATCH_POSSIBLE_EXCEPTIONS(logstr) \
|
|
catch (std::invalid_argument & e0) \
|
|
{ \
|
|
logstr = e0.what(); \
|
|
return_value = McResult::MC_INVALID_VALUE; \
|
|
} \
|
|
catch (std::runtime_error & e1) \
|
|
{ \
|
|
logstr = e1.what(); \
|
|
return_value = McResult::MC_INVALID_OPERATION; \
|
|
} \
|
|
catch (std::logic_error & e2) \
|
|
{ \
|
|
logstr = e2.what(); \
|
|
return_value = McResult::MC_RESULT_MAX_ENUM; \
|
|
} \
|
|
catch (std::exception & e3) \
|
|
{ \
|
|
logstr = e3.what(); \
|
|
return_value = McResult::MC_RESULT_MAX_ENUM; \
|
|
}
|
|
|
|
extern thread_local std::string per_thread_api_log_str; // frontend.cpp
|
|
|
|
extern "C" void create_context_impl(
|
|
McContext* pContext, McFlags flags, uint32_t num_helper_threads) noexcept(false);
|
|
|
|
extern "C" void debug_message_callback_impl(
|
|
McContext context,
|
|
pfn_mcDebugOutput_CALLBACK cb,
|
|
const McVoid* userParam) noexcept(false);
|
|
|
|
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);
|
|
|
|
extern "C" void debug_message_control_impl(
|
|
McContext context,
|
|
McDebugSource source,
|
|
McDebugType type,
|
|
McDebugSeverity severity,
|
|
bool enabled) noexcept(false);
|
|
|
|
extern "C" void get_info_impl(
|
|
const McContext context,
|
|
McFlags info,
|
|
McSize bytes,
|
|
McVoid* pMem,
|
|
McSize* pNumBytes) noexcept(false);
|
|
|
|
extern "C" void bind_state_impl(
|
|
const McContext context,
|
|
McFlags stateInfo,
|
|
McSize bytes,
|
|
const McVoid* pMem);
|
|
|
|
extern "C" void create_user_event_impl(McEvent* event, McContext context);
|
|
|
|
extern "C" void set_user_event_status_impl(McEvent event, McInt32 execution_status);
|
|
|
|
extern "C" void get_event_info_impl(
|
|
const McEvent event,
|
|
McFlags info,
|
|
McSize bytes,
|
|
McVoid* pMem,
|
|
McSize* pNumBytes) noexcept(false);
|
|
|
|
extern "C" void set_event_callback_impl(
|
|
McEvent eventHandle,
|
|
pfn_McEvent_CALLBACK eventCallback,
|
|
McVoid* data);
|
|
|
|
extern "C" void wait_for_events_impl(
|
|
uint32_t numEventsInWaitlist,
|
|
const McEvent* pEventWaitList,
|
|
McResult& runtimeStatusFromAllPrecedingEvents) noexcept(false);
|
|
|
|
extern "C" void dispatch_impl(
|
|
McContext context,
|
|
McFlags flags,
|
|
const McVoid* pSrcMeshVertices,
|
|
const uint32_t* pSrcMeshFaceIndices,
|
|
const uint32_t* pSrcMeshFaceSizes,
|
|
uint32_t numSrcMeshVertices,
|
|
uint32_t numSrcMeshFaces,
|
|
const McVoid* pCutMeshVertices,
|
|
const uint32_t* pCutMeshFaceIndices,
|
|
const uint32_t* pCutMeshFaceSizes,
|
|
uint32_t numCutMeshVertices,
|
|
uint32_t numCutMeshFaces,
|
|
uint32_t numEventsInWaitlist,
|
|
const McEvent* pEventWaitList,
|
|
McEvent* pEvent) noexcept(false);
|
|
|
|
extern "C" void dispatch_planar_section_impl(
|
|
McContext context,
|
|
McFlags flags,
|
|
const McVoid* pSrcMeshVertices,
|
|
const uint32_t* pSrcMeshFaceIndices,
|
|
const uint32_t* pSrcMeshFaceSizes,
|
|
uint32_t numSrcMeshVertices,
|
|
uint32_t numSrcMeshFaces,
|
|
const McDouble* pNormalVector,
|
|
const McDouble sectionOffset,
|
|
uint32_t numEventsInWaitlist,
|
|
const McEvent* pEventWaitList,
|
|
McEvent* pEvent) noexcept(false);
|
|
|
|
extern "C" void get_connected_components_impl(
|
|
const McContext context,
|
|
const McConnectedComponentType connectedComponentType,
|
|
const uint32_t numEntries,
|
|
McConnectedComponent* pConnComps,
|
|
uint32_t* numConnComps,
|
|
uint32_t numEventsInWaitlist,
|
|
const McEvent* pEventWaitList,
|
|
McEvent* pEvent) noexcept(false);
|
|
|
|
extern "C" void get_connected_component_data_impl(
|
|
const McContext context,
|
|
const McConnectedComponent connCompId,
|
|
McFlags flags,
|
|
McSize bytes,
|
|
McVoid* pMem,
|
|
McSize* pNumBytes,
|
|
uint32_t numEventsInWaitlist,
|
|
const McEvent* pEventWaitList,
|
|
McEvent* pEvent) noexcept(false);
|
|
|
|
extern "C" void release_connected_components_impl(
|
|
const McContext context,
|
|
uint32_t numConnComps,
|
|
const McConnectedComponent* pConnComps) noexcept(false);
|
|
|
|
extern "C" void release_context_impl(
|
|
McContext context) noexcept(false);
|
|
|
|
extern "C" void release_events_impl(uint32_t numEvents, const McEvent* pEvents);
|
|
|
|
// base struct from which other structs represent connected components inherit
|
|
struct connected_component_t {
|
|
virtual ~connected_component_t() {};
|
|
McConnectedComponentType type = (McConnectedComponentType)0;
|
|
McConnectedComponent m_user_handle = MC_NULL_HANDLE;
|
|
// array_mesh_t indexArrayMesh;
|
|
// hmesh_t mesh;
|
|
std::shared_ptr<output_mesh_info_t> kernel_hmesh_data;
|
|
|
|
//
|
|
std::shared_ptr< //
|
|
std::unordered_map< //
|
|
fd_t /*child face*/,
|
|
fd_t /*parent face in the [user-provided] source mesh*/
|
|
> //
|
|
>
|
|
source_hmesh_child_to_usermesh_birth_face; // fpPartitionChildFaceToCorrespondingInputSrcMeshFace
|
|
std::shared_ptr< //
|
|
std::unordered_map< //
|
|
fd_t /*child face*/,
|
|
fd_t /*parent face in the [user-provided] cut mesh*/
|
|
>>
|
|
cut_hmesh_child_to_usermesh_birth_face; // fpPartitionChildFaceToCorrespondingInputCutMeshFace
|
|
// descriptors and coordinates of new vertices that are added into an input mesh (source mesh or cut mesh)
|
|
// in order to carry out partitioning
|
|
std::shared_ptr<std::unordered_map<vd_t, vec3>> source_hmesh_new_poly_partition_vertices; // addedFpPartitioningVerticesOnCorrespondingInputSrcMesh
|
|
std::shared_ptr<std::unordered_map<vd_t, vec3>> cut_hmesh_new_poly_partition_vertices; // addedFpPartitioningVerticesOnCorrespondingInputCutMesh
|
|
uint32_t internal_sourcemesh_vertex_count; // init from source_hmesh.number_of_vertices()
|
|
uint32_t client_sourcemesh_vertex_count; // init from numSrcMeshVertices
|
|
uint32_t internal_sourcemesh_face_count; // init from source_hmesh.number_of_faces()
|
|
uint32_t client_sourcemesh_face_count; // init from source_hmesh_face_count OR numSrcMeshFaces
|
|
// Stores the contiguous array of unsigned integers that define
|
|
// a triangulation of all [non-triangle faces] of the connected component.
|
|
// This vector is only populated if client invokes mcGetConnectedComponnentData
|
|
// with flag MC_CONNECTED_COMPONENT_DATA_FACE_TRIANGULATION and has the effect of
|
|
// triangulating every non-triangle face in the connected component.
|
|
std::vector<uint32_t> cdt_index_cache;
|
|
bool cdt_index_cache_initialized = false;
|
|
// stores the mapping between a CDT triangle in the connected component and
|
|
// the original "birth-face" in an input mesh (source mesh or cut mesh)
|
|
std::vector<uint32_t> cdt_face_map_cache;
|
|
bool cdt_face_map_cache_initialized = false;
|
|
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
// Stores the number of vertices per face of CC. This is an optimization
|
|
// because there is a possibility that face-sizes may (at-minimum) be queried
|
|
// twice by user. The first case is during the populating (i.e. second) call to the API
|
|
// mcGetConnectedComponentData(..., MC_CONNECTED_COMPONENT_DATA_FACE_SIZE, ...);
|
|
// The second case is during the populating (i.e. second) call to the API
|
|
// mcGetConnectedComponentData(..., MC_CONNECTED_COMPONENT_DATA_FACE, ...);.
|
|
// The key detail here is that the second case requires knowledge of the
|
|
// number of vertices in each face in order to know how to schedule parallel
|
|
// work with prefix-sums etc.. Thus, the optimization is useful only if
|
|
// building MCUT with multi-threading
|
|
std::vector<uint32_t> face_sizes_cache;
|
|
bool face_sizes_cache_initialized = false;
|
|
// see documentation of face_sizes_cache above
|
|
// Similar concepts but applied to MC_CONNECTED_COMPONENT_DATA_FACE_ADJACENT_FACE
|
|
std::vector<uint32_t> face_adjacent_faces_size_cache;
|
|
bool face_adjacent_faces_size_cache_initialized = false;
|
|
#endif // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
// non-zero if origin source and cut-mesh where perturbed
|
|
vec3 perturbation_vector = vec3(0.0);
|
|
};
|
|
|
|
// struct representing a fragment
|
|
struct fragment_cc_t : public connected_component_t {
|
|
McFragmentLocation fragmentLocation = (McFragmentLocation)0;
|
|
McFragmentSealType srcMeshSealType = (McFragmentSealType)0;
|
|
McPatchLocation patchLocation = (McPatchLocation)0;
|
|
};
|
|
|
|
// struct representing a patch
|
|
struct patch_cc_t : public connected_component_t {
|
|
McPatchLocation patchLocation = (McPatchLocation)0;
|
|
};
|
|
|
|
// struct representing a seam
|
|
struct seam_cc_t : public connected_component_t {
|
|
McSeamOrigin origin = (McSeamOrigin)0;
|
|
};
|
|
|
|
// struct representing an input (user provided mesh)
|
|
struct input_cc_t : public connected_component_t {
|
|
McInputOrigin origin = (McInputOrigin)0;
|
|
};
|
|
|
|
struct event_t {
|
|
std::future<void> m_future; // used to wait on event
|
|
// used to synchronise access to variables associated with the callback
|
|
// function.
|
|
// This also also allows us to overcome the edgecase that mcSetEventCallback
|
|
// is called after the task associated with an event has been completed,
|
|
// in which case the new callback will be invoked immediately.
|
|
// see "set_callback_data()" below
|
|
std::mutex m_callback_mutex;
|
|
struct {
|
|
// optional user callback, which is invoked when associated task is finished
|
|
pfn_McEvent_CALLBACK m_fn_ptr;
|
|
// pointer passed to user provided callback function
|
|
McVoid* m_data_ptr;
|
|
// atomic boolean flag indicating whether the callback associated with event
|
|
// object has been called
|
|
std::atomic<bool> m_invoked;
|
|
} m_callback_info;
|
|
// atomic boolean flag indicating whether the task associated with event
|
|
// object has completed running
|
|
std::atomic<bool> m_finished;
|
|
McEvent m_user_handle; // handle used by client app to reference this event object
|
|
// the Manager thread which was assigned the task of managing the task associated with this event object.
|
|
std::atomic<uint32_t> m_responsible_thread_id;
|
|
std::atomic<int32_t> m_runtime_exec_status; // API return code associated with respective task (for user to query)
|
|
std::atomic<size_t> m_timestamp_submit;
|
|
std::atomic<size_t> m_timestamp_start;
|
|
std::atomic<size_t> m_timestamp_end;
|
|
std::atomic<uint32_t> m_command_exec_status;
|
|
bool m_profiling_enabled;
|
|
McCommandType m_command_type;
|
|
// A callable object that also holds an std::future. Its purpose is to emulate
|
|
// the internal representation of an API task, where this time the task is actually
|
|
// a user command since this pointer is define ONLY for user events.
|
|
// The std::future object of the packaged task is used to initialize m_future
|
|
// when this event is a user event. This our internal mechanism allowing for
|
|
// API command to be able to effectively wait on user events.
|
|
std::unique_ptr<std::packaged_task<void()>> m_user_API_command_task_emulator;
|
|
McContext m_context;
|
|
|
|
const char* get_cmd_type_str()
|
|
{
|
|
switch (m_command_type) {
|
|
case McCommandType::MC_COMMAND_DISPATCH: {
|
|
return "MC_COMMAND_DISPATCH";
|
|
} break;
|
|
case McCommandType::MC_COMMAND_GET_CONNECTED_COMPONENT_DATA: {
|
|
return "MC_COMMAND_GET_CONNECTED_COMPONENT_DATA";
|
|
} break;
|
|
case McCommandType::MC_COMMAND_GET_CONNECTED_COMPONENTS: {
|
|
return "MC_COMMAND_GET_CONNECTED_COMPONENTS";
|
|
} break;
|
|
case McCommandType::MC_COMMAND_USER: {
|
|
return "MC_COMMAND_USER";
|
|
} break;
|
|
case McCommandType::MC_COMMAND_UKNOWN: {
|
|
return "MC_COMMAND_UKNOWN";
|
|
} break;
|
|
default:
|
|
fprintf(stderr, "unknown command type value (%d)\n", (int)m_command_type);
|
|
return "UNKNOWN VALUE";
|
|
}
|
|
}
|
|
explicit event_t(McEvent user_handle, McCommandType command_type)
|
|
: m_user_handle(user_handle)
|
|
, m_responsible_thread_id(UINT32_MAX)
|
|
, m_runtime_exec_status(MC_NO_ERROR)
|
|
, m_timestamp_submit(0)
|
|
, m_timestamp_start(0)
|
|
, m_timestamp_end(0)
|
|
, m_command_exec_status(MC_RESULT_MAX_ENUM)
|
|
, m_profiling_enabled(true)
|
|
, m_command_type(command_type)
|
|
, m_user_API_command_task_emulator(nullptr)
|
|
, m_context(nullptr)
|
|
{
|
|
log_msg("[MCUT] Create event (type=" << get_cmd_type_str() <<", handle=" << m_user_handle << ")");
|
|
|
|
m_callback_info.m_fn_ptr = nullptr;
|
|
m_callback_info.m_data_ptr = nullptr;
|
|
m_finished.store(false);
|
|
m_callback_info.m_invoked.store(true); // so that we do not call a null pointer/needless invoke the callback in the destructor
|
|
}
|
|
|
|
~event_t()
|
|
{
|
|
|
|
if (m_callback_info.m_invoked.load() == false && m_callback_info.m_fn_ptr != nullptr && m_runtime_exec_status.load() == MC_NO_ERROR) {
|
|
MCUT_ASSERT(m_user_handle != MC_NULL_HANDLE);
|
|
(*(m_callback_info.m_fn_ptr))(m_user_handle, m_callback_info.m_data_ptr);
|
|
}
|
|
|
|
log_msg("[MCUT] Destroy event (type=" << get_cmd_type_str() << ", handle=" << m_user_handle << ")");
|
|
}
|
|
|
|
inline std::size_t get_time_since_epoch()
|
|
{
|
|
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
|
}
|
|
|
|
inline void log_submit_time()
|
|
{
|
|
if (m_profiling_enabled) {
|
|
this->m_timestamp_submit.store(get_time_since_epoch());
|
|
}
|
|
// TODO: use specific acquire-release semantics
|
|
// see e.g.: https://stackoverflow.com/questions/13632344/understanding-c11-memory-fences
|
|
m_command_exec_status.store(McEventCommandExecStatus::MC_SUBMITTED);
|
|
}
|
|
|
|
inline void log_start_time()
|
|
{
|
|
if (m_profiling_enabled) {
|
|
this->m_timestamp_start.store(get_time_since_epoch());
|
|
}
|
|
m_command_exec_status = McEventCommandExecStatus::MC_RUNNING;
|
|
}
|
|
|
|
// logs time and sets m_command_exec_status to MC_COMPLETE
|
|
inline void log_end_time()
|
|
{
|
|
if (m_profiling_enabled) {
|
|
this->m_timestamp_end.store(get_time_since_epoch());
|
|
}
|
|
m_command_exec_status.store(McEventCommandExecStatus::MC_COMPLETE);
|
|
}
|
|
|
|
// thread-safe function to set the callback function for an event object
|
|
void set_callback_data(McEvent handle, pfn_McEvent_CALLBACK fn_ptr, McVoid* data_ptr)
|
|
{
|
|
std::lock_guard<std::mutex> lock(m_callback_mutex); // exclusive access
|
|
|
|
m_user_handle = handle;
|
|
m_callback_info.m_fn_ptr = fn_ptr;
|
|
m_callback_info.m_data_ptr = data_ptr;
|
|
m_callback_info.m_invoked.store(false);
|
|
|
|
if (m_finished.load() == true) { // see mutex documentation
|
|
// immediately invoke the callback
|
|
(*(m_callback_info.m_fn_ptr))(m_user_handle, m_callback_info.m_data_ptr);
|
|
m_callback_info.m_invoked.store(true);
|
|
}
|
|
}
|
|
|
|
// update the status of the event object to "finished"
|
|
void notify_task_complete(McResult exec_status)
|
|
{
|
|
m_finished = true;
|
|
m_runtime_exec_status = exec_status;
|
|
|
|
std::lock_guard<std::mutex> lock(m_callback_mutex);
|
|
if (m_callback_info.m_invoked.load() == false && m_callback_info.m_fn_ptr != nullptr) {
|
|
MCUT_ASSERT(m_user_handle != MC_NULL_HANDLE);
|
|
(*(m_callback_info.m_fn_ptr))(m_user_handle, m_callback_info.m_data_ptr);
|
|
m_callback_info.m_invoked.store(true);
|
|
}
|
|
}
|
|
};
|
|
|
|
// init in frontened.cpp
|
|
extern threadsafe_list<std::shared_ptr<event_t>> g_events;
|
|
extern std::atomic<std::uintptr_t> g_objects_counter; // a counter that is used to assign a unique value to a McObject handle that will be returned to the user
|
|
extern std::once_flag g_objects_counter_init_flag;
|
|
|
|
// our custome deleter function for std::unique_ptr variable of an array type
|
|
template <typename Derived>
|
|
void fn_delete_cc(connected_component_t* p)
|
|
{
|
|
log_msg("[MCUT] Destroy connected component " << p->m_user_handle);
|
|
delete dynamic_cast<Derived*>(p);
|
|
}
|
|
|
|
// struct defining the state of a context object
|
|
struct context_t {
|
|
private:
|
|
std::atomic<bool> m_done;
|
|
std::vector<thread_safe_queue<function_wrapper>> m_queues;
|
|
// Master/Manager thread(s) which are responsible for running the API calls
|
|
// 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
|
|
// 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"
|
|
std::vector<std::thread> m_api_threads;
|
|
// join_threads m_joiner;
|
|
|
|
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
// A pool of threads that is shared by manager threads to execute e.g. parallel
|
|
// section of MCUT API tasks (see e.g. frontend.cpp and kernel.cpp)
|
|
std::unique_ptr<thread_pool> m_compute_threadpool;
|
|
#endif
|
|
|
|
// The state and flag variable current used to configure the next dispatch call
|
|
McFlags m_flags = (McFlags)0;
|
|
|
|
std::atomic<McDouble> m_general_position_enforcement_constant;
|
|
std::atomic<McUint32> m_max_num_perturbation_attempts;
|
|
std::atomic<McConnectedComponentFaceWindingOrder> m_connected_component_winding_order;
|
|
|
|
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
|
|
// 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)) {
|
|
m_queues[thread_id].wait_and_pop(task);
|
|
}
|
|
|
|
if (m_done) {
|
|
break;
|
|
}
|
|
|
|
task();
|
|
|
|
} while (true);
|
|
|
|
log_msg("[MCUT] Shutdown API thread " << std::this_thread::get_id() << " (" << thread_id << ")");
|
|
}
|
|
|
|
public:
|
|
context_t(McContext handle, McFlags flags
|
|
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
,
|
|
uint32_t num_compute_threads
|
|
#endif
|
|
)
|
|
: 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_connected_component_winding_order(McConnectedComponentFaceWindingOrder::MC_CONNECTED_COMPONENT_FACE_WINDING_ORDER_AS_GIVEN)
|
|
, m_user_handle(handle)
|
|
, dbgCallbackBitfieldSource(0)
|
|
, dbgCallbackBitfieldType(0)
|
|
, dbgCallbackBitfieldSeverity(0)
|
|
{
|
|
log_msg("\n[MCUT] Create context " << m_user_handle);
|
|
|
|
try {
|
|
const uint32_t manager_thread_count = (flags & MC_OUT_OF_ORDER_EXEC_MODE_ENABLE) ? 2 : 1;
|
|
|
|
m_queues = std::vector<thread_safe_queue<function_wrapper>>(manager_thread_count);
|
|
|
|
for (uint32_t i = 0; i < manager_thread_count; ++i) {
|
|
m_queues[i].set_done_ptr(&m_done);
|
|
m_api_threads.push_back(std::thread(&context_t::api_thread_main, this, i));
|
|
}
|
|
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
// create the pool of compute threads. These are the worker threads that
|
|
// can be tasked with work from any manager-thread. Thus, manager threads
|
|
// share the available/user-specified compute threads.
|
|
m_compute_threadpool = std::unique_ptr<thread_pool>(new thread_pool(num_compute_threads, manager_thread_count));
|
|
#endif
|
|
} catch (...) {
|
|
shutdown();
|
|
|
|
log_msg("[MCUT] Destroy context due to exception" << m_user_handle);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
~context_t()
|
|
{
|
|
shutdown();
|
|
log_msg("[MCUT] Destroy context " << m_user_handle);
|
|
}
|
|
|
|
void shutdown()
|
|
{
|
|
m_done = true;
|
|
|
|
std::atomic_thread_fence(std::memory_order_acq_rel);
|
|
|
|
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
m_compute_threadpool.reset();
|
|
#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();
|
|
}
|
|
}
|
|
}
|
|
|
|
McContext m_user_handle;
|
|
|
|
// returns the flags which determine the runtime configuration of this context
|
|
const McFlags& get_flags() const
|
|
{
|
|
return this->m_flags;
|
|
}
|
|
|
|
// returns (user controllable) epsilon representing the maximum by which the cut-mesh
|
|
// can be perturbed on any axis
|
|
McDouble get_general_position_enforcement_constant() const
|
|
{
|
|
return this->m_general_position_enforcement_constant.load(std::memory_order_acquire);
|
|
}
|
|
|
|
void set_general_position_enforcement_constant(McDouble new_value)
|
|
{
|
|
this->m_general_position_enforcement_constant.store(new_value, std::memory_order_release);
|
|
}
|
|
|
|
// returns (user controllable) maximum number of times by which the cut-mesh
|
|
// can be perturbed before giving (input likely need to be preprocessed)
|
|
McUint32 get_general_position_enforcement_attempts() const
|
|
{
|
|
return this->m_max_num_perturbation_attempts.load(std::memory_order_acquire);
|
|
}
|
|
|
|
void set_general_position_enforcement_attempts(McUint32 new_value)
|
|
{
|
|
this->m_max_num_perturbation_attempts.store(new_value, std::memory_order_release);
|
|
}
|
|
|
|
McConnectedComponentFaceWindingOrder get_connected_component_winding_order() const
|
|
{
|
|
return this->m_connected_component_winding_order.load(std::memory_order_acquire);
|
|
}
|
|
|
|
void set_connected_component_winding_order(McConnectedComponentFaceWindingOrder new_value)
|
|
{
|
|
this->m_connected_component_winding_order.store(new_value, std::memory_order_release);
|
|
}
|
|
|
|
|
|
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
thread_pool& get_shared_compute_threadpool()
|
|
{
|
|
return m_compute_threadpool.get()[0];
|
|
}
|
|
#endif
|
|
|
|
template <typename FunctionType>
|
|
McEvent prepare_and_submit_API_task(McCommandType cmdType, uint32_t numEventsInWaitlist, const McEvent* pEventWaitList, FunctionType api_fn)
|
|
{
|
|
//
|
|
// 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)),
|
|
cmdType));
|
|
|
|
MCUT_ASSERT(event_ptr != nullptr);
|
|
|
|
g_events.push_front(event_ptr);
|
|
|
|
//event_ptr->m_user_handle = reinterpret_cast<McEvent>(g_objects_counter.fetch_add(1, std::memory_order_relaxed));
|
|
event_ptr->m_profiling_enabled = (this->m_flags & MC_PROFILING_ENABLE) != 0;
|
|
// event_ptr->m_command_type = cmdType;
|
|
|
|
event_ptr->log_submit_time();
|
|
|
|
// List of events the enqueued task depends on
|
|
//
|
|
// local copy that will be captured by-value (user permitted to re-use pEventWaitList)
|
|
const std::vector<McEvent> event_waitlist(pEventWaitList, pEventWaitList + numEventsInWaitlist);
|
|
|
|
//
|
|
// Determine which manager thread to assign the task
|
|
//
|
|
|
|
// the id of manager thread that will be assigned the current task
|
|
uint32_t responsible_thread_id = UINT32_MAX;
|
|
|
|
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;
|
|
|
|
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");
|
|
}
|
|
|
|
const bool parent_task_is_not_finished = parent_task_event_ptr->m_finished.load() == false;
|
|
|
|
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();
|
|
|
|
MCUT_ASSERT(responsible_thread_id != UINT32_MAX);
|
|
MCUT_ASSERT(responsible_thread_id < (uint32_t)m_api_threads.size());
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
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;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (thread_with_empty_queue != UINT32_MAX) {
|
|
responsible_thread_id = thread_with_empty_queue;
|
|
} else { // all threads have work to do
|
|
responsible_thread_id = 0; // just pick thread 0
|
|
}
|
|
}
|
|
|
|
//
|
|
// Package-up the task as a synchronised operation that will wait for
|
|
// other tasks in the event_waitlist, compute the operation, and finally update
|
|
// the respective event state with the completion status.
|
|
//
|
|
|
|
std::weak_ptr<event_t> event_weak_ptr(event_ptr);
|
|
|
|
std::packaged_task<void()> task(
|
|
[=]() {
|
|
McResult runtime_status_from_all_preceding_events = McResult::MC_NO_ERROR;
|
|
|
|
if (!event_waitlist.empty()) {
|
|
wait_for_events_impl((uint32_t)event_waitlist.size(), &event_waitlist[0], runtime_status_from_all_preceding_events); // block until events are done
|
|
}
|
|
|
|
// if any previous event failed then we cannot proceed with this task.
|
|
// i.e. no-Op
|
|
if (runtime_status_from_all_preceding_events != McResult::MC_NO_ERROR) {
|
|
return;
|
|
}
|
|
|
|
MCUT_ASSERT(!event_weak_ptr.expired());
|
|
|
|
{
|
|
std::shared_ptr<event_t> event = event_weak_ptr.lock();
|
|
|
|
MCUT_ASSERT(event != nullptr);
|
|
|
|
{
|
|
McResult return_value = McResult::MC_NO_ERROR;
|
|
per_thread_api_log_str.clear();
|
|
|
|
event->log_start_time();
|
|
|
|
try {
|
|
api_fn(); // execute the API function.
|
|
}
|
|
CATCH_POSSIBLE_EXCEPTIONS(per_thread_api_log_str); // exceptions may be thrown due to runtime errors, which must be reported back to user
|
|
|
|
if (!per_thread_api_log_str.empty()) {
|
|
|
|
std::fprintf(stderr, "%s(...) -> %s (EventID=%p)\n", __FUNCTION__, per_thread_api_log_str.c_str(), event == nullptr ? (McEvent)0 : event->m_user_handle);
|
|
|
|
if (return_value == McResult::MC_NO_ERROR) // i.e. problem with basic local parameter checks
|
|
{
|
|
return_value = McResult::MC_INVALID_VALUE;
|
|
}
|
|
}
|
|
|
|
event->notify_task_complete(return_value); // updated event state to indicate task completion (lock-based)
|
|
event->log_end_time();
|
|
}
|
|
}
|
|
});
|
|
|
|
event_ptr->m_future = task.get_future(); // the future we can later wait on via mcWaitForEVents
|
|
event_ptr->m_responsible_thread_id = responsible_thread_id;
|
|
|
|
m_queues[responsible_thread_id].push(std::move(task)); // enqueue task to be executed when responsible API thread is free
|
|
|
|
return event_ptr->m_user_handle;
|
|
}
|
|
|
|
// the current set of connected components associated with context
|
|
threadsafe_list<std::shared_ptr<connected_component_t>> connected_components;
|
|
|
|
// McFlags dispatchFlags = (McFlags)0;
|
|
|
|
// client/user debugging variables
|
|
// ------------------------------
|
|
|
|
// function pointer to user-define callback function for status/erro reporting
|
|
pfn_mcDebugOutput_CALLBACK debugCallback = nullptr;
|
|
// user provided data for callback
|
|
const McVoid* debugCallbackUserParam = nullptr;
|
|
|
|
std::mutex debugCallbackMutex;
|
|
// controller for permmited messages based on the source of message
|
|
std::atomic<McFlags> dbgCallbackBitfieldSource;
|
|
// controller for permmited messages based on the type of message
|
|
std::atomic<McFlags> dbgCallbackBitfieldType;
|
|
// controller for permmited messages based on the severity of message
|
|
std::atomic<McFlags> dbgCallbackBitfieldSeverity;
|
|
bool dbgCallbackAllowAsyncCalls = true;
|
|
|
|
void set_debug_callback_data(pfn_mcDebugOutput_CALLBACK cb, const McVoid* data_ptr)
|
|
{
|
|
std::lock_guard<std::mutex> lguard(debugCallbackMutex);
|
|
debugCallback = cb;
|
|
debugCallbackUserParam = data_ptr;
|
|
}
|
|
|
|
struct debug_log_msg_t {
|
|
McDebugSource source;
|
|
McDebugType type;
|
|
McDebugSeverity severity;
|
|
std::string str;
|
|
};
|
|
|
|
std::vector<debug_log_msg_t> m_debug_logs;
|
|
|
|
// function to invoke the user-provided debug call back
|
|
void dbg_cb(McDebugSource source,
|
|
McDebugType type,
|
|
unsigned int id, // unused
|
|
McDebugSeverity severity,
|
|
const std::string& message)
|
|
{
|
|
if (this->m_flags & McContextCreationFlags::MC_DEBUG) // information logged only during debug mode
|
|
{
|
|
std::unique_lock<std::mutex> ulock(debugCallbackMutex, std::defer_lock);
|
|
|
|
if (!dbgCallbackAllowAsyncCalls) {
|
|
ulock.lock();
|
|
} // otherwise the callback will be invoked asynchronously
|
|
|
|
// can we log this type of message? (based on user preferences via mcDebugMessageControl)
|
|
const bool canLog = ((uint32_t)source & dbgCallbackBitfieldSource.load(std::memory_order_acquire)) && //
|
|
((uint32_t)type & dbgCallbackBitfieldType.load(std::memory_order_acquire)) && //
|
|
((uint32_t)severity & dbgCallbackBitfieldSeverity.load(std::memory_order_acquire));
|
|
|
|
if (canLog) {
|
|
|
|
if (debugCallback != nullptr) { // user gave us a callback function pointer
|
|
|
|
(*debugCallback)(source, type, id, severity, message.length(), message.c_str(), debugCallbackUserParam);
|
|
|
|
} else // write to the internal log
|
|
{
|
|
m_debug_logs.emplace_back(debug_log_msg_t());
|
|
|
|
debug_log_msg_t& dbg_log = m_debug_logs.back();
|
|
|
|
dbg_log.source = source;
|
|
dbg_log.type = type;
|
|
dbg_log.severity = severity;
|
|
dbg_log.str = message;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// list of contexts created by client/user
|
|
extern "C" threadsafe_list<std::shared_ptr<context_t>> g_contexts;
|
|
|
|
#endif // #ifndef _FRONTEND_H_
|