Fix potential integer underflow in rounded up divisions

A new `Math::division_round_up()` function was added, allowing for easy
and correct computation of integer divisions when the result needs to
be rounded up.

Fixes #80358.

Co-authored-by: Rémi Verschelde <rverschelde@gmail.com>
This commit is contained in:
EddieBreeg 2023-08-07 20:19:20 +02:00 committed by Rémi Verschelde
parent 13a0d6e9b2
commit 8747c67d9e
No known key found for this signature in database
GPG key ID: C3336907360768E1
14 changed files with 81 additions and 41 deletions

View file

@ -198,6 +198,22 @@ public:
#endif
}
// These methods assume (p_num + p_den) doesn't overflow.
static _ALWAYS_INLINE_ int32_t division_round_up(int32_t p_num, int32_t p_den) {
int32_t offset = (p_num < 0 && p_den < 0) ? 1 : -1;
return (p_num + p_den + offset) / p_den;
}
static _ALWAYS_INLINE_ uint32_t division_round_up(uint32_t p_num, uint32_t p_den) {
return (p_num + p_den - 1) / p_den;
}
static _ALWAYS_INLINE_ int64_t division_round_up(int64_t p_num, int64_t p_den) {
int32_t offset = (p_num < 0 && p_den < 0) ? 1 : -1;
return (p_num + p_den + offset) / p_den;
}
static _ALWAYS_INLINE_ uint64_t division_round_up(uint64_t p_num, uint64_t p_den) {
return (p_num + p_den - 1) / p_den;
}
static _ALWAYS_INLINE_ bool is_finite(double p_val) { return isfinite(p_val); }
static _ALWAYS_INLINE_ bool is_finite(float p_val) { return isfinite(p_val); }

View file

@ -1538,7 +1538,7 @@ void MeshStorage::_multimesh_make_local(MultiMesh *multimesh) const {
memset(w, 0, (size_t)multimesh->instances * multimesh->stride_cache * sizeof(float));
}
}
uint32_t data_cache_dirty_region_count = (multimesh->instances - 1) / MULTIMESH_DIRTY_REGION_SIZE + 1;
uint32_t data_cache_dirty_region_count = Math::division_round_up(multimesh->instances, MULTIMESH_DIRTY_REGION_SIZE);
multimesh->data_cache_dirty_regions = memnew_arr(bool, data_cache_dirty_region_count);
for (uint32_t i = 0; i < data_cache_dirty_region_count; i++) {
multimesh->data_cache_dirty_regions[i] = false;
@ -1549,7 +1549,7 @@ void MeshStorage::_multimesh_make_local(MultiMesh *multimesh) const {
void MeshStorage::_multimesh_mark_dirty(MultiMesh *multimesh, int p_index, bool p_aabb) {
uint32_t region_index = p_index / MULTIMESH_DIRTY_REGION_SIZE;
#ifdef DEBUG_ENABLED
uint32_t data_cache_dirty_region_count = (multimesh->instances - 1) / MULTIMESH_DIRTY_REGION_SIZE + 1;
uint32_t data_cache_dirty_region_count = Math::division_round_up(multimesh->instances, MULTIMESH_DIRTY_REGION_SIZE);
ERR_FAIL_UNSIGNED_INDEX(region_index, data_cache_dirty_region_count); //bug
#endif
if (!multimesh->data_cache_dirty_regions[region_index]) {
@ -1570,7 +1570,7 @@ void MeshStorage::_multimesh_mark_dirty(MultiMesh *multimesh, int p_index, bool
void MeshStorage::_multimesh_mark_all_dirty(MultiMesh *multimesh, bool p_data, bool p_aabb) {
if (p_data) {
uint32_t data_cache_dirty_region_count = (multimesh->instances - 1) / MULTIMESH_DIRTY_REGION_SIZE + 1;
uint32_t data_cache_dirty_region_count = Math::division_round_up(multimesh->instances, MULTIMESH_DIRTY_REGION_SIZE);
for (uint32_t i = 0; i < data_cache_dirty_region_count; i++) {
if (!multimesh->data_cache_dirty_regions[i]) {
@ -1917,7 +1917,7 @@ void MeshStorage::multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_b
multimesh->data_cache = multimesh->data_cache;
{
//clear dirty since nothing will be dirty anymore
uint32_t data_cache_dirty_region_count = (multimesh->instances - 1) / MULTIMESH_DIRTY_REGION_SIZE + 1;
uint32_t data_cache_dirty_region_count = Math::division_round_up(multimesh->instances, MULTIMESH_DIRTY_REGION_SIZE);
for (uint32_t i = 0; i < data_cache_dirty_region_count; i++) {
multimesh->data_cache_dirty_regions[i] = false;
}
@ -2044,8 +2044,8 @@ void MeshStorage::_update_dirty_multimeshes() {
uint32_t visible_instances = multimesh->visible_instances >= 0 ? multimesh->visible_instances : multimesh->instances;
if (multimesh->data_cache_used_dirty_regions) {
uint32_t data_cache_dirty_region_count = (multimesh->instances - 1) / MULTIMESH_DIRTY_REGION_SIZE + 1;
uint32_t visible_region_count = visible_instances == 0 ? 0 : (visible_instances - 1) / MULTIMESH_DIRTY_REGION_SIZE + 1;
uint32_t data_cache_dirty_region_count = Math::division_round_up(multimesh->instances, (int)MULTIMESH_DIRTY_REGION_SIZE);
uint32_t visible_region_count = visible_instances == 0 ? 0 : Math::division_round_up(visible_instances, (uint32_t)MULTIMESH_DIRTY_REGION_SIZE);
GLint region_size = multimesh->stride_cache * MULTIMESH_DIRTY_REGION_SIZE * sizeof(float);

View file

@ -194,9 +194,9 @@ struct GDScriptUtilityFunctionsDefinitions {
// Calculate how many.
int count = 0;
if (incr > 0) {
count = ((to - from - 1) / incr) + 1;
count = Math::division_round_up(to - from, incr);
} else {
count = ((from - to - 1) / -incr) + 1;
count = Math::division_round_up(from - to, -incr);
}
Error err = arr.resize(count);

View file

@ -756,7 +756,7 @@ LightmapperRD::BakeError LightmapperRD::_dilate(RenderingDevice *rd, Ref<RDShade
rd->compute_list_bind_uniform_set(compute_list, dilate_uniform_set, 1);
push_constant.region_ofs[0] = 0;
push_constant.region_ofs[1] = 0;
Vector3i group_size((atlas_size.x - 1) / 8 + 1, (atlas_size.y - 1) / 8 + 1, 1); //restore group size
Vector3i group_size(Math::division_round_up(atlas_size.x, 8), Math::division_round_up(atlas_size.y, 8), 1); //restore group size
for (int i = 0; i < atlas_slices; i++) {
push_constant.atlas_slice = i;
@ -939,8 +939,8 @@ LightmapperRD::BakeError LightmapperRD::_denoise(RenderingDevice *p_rd, Ref<RDSh
// We use a region with 1/4 the amount of pixels if we're denoising SH lightmaps, as
// all four of them are denoised in the shader in one dispatch.
const int max_region_size = p_bake_sh ? 512 : 1024;
int x_regions = (p_atlas_size.width - 1) / max_region_size + 1;
int y_regions = (p_atlas_size.height - 1) / max_region_size + 1;
int x_regions = Math::division_round_up(p_atlas_size.width, max_region_size);
int y_regions = Math::division_round_up(p_atlas_size.height, max_region_size);
for (int s = 0; s < p_atlas_slices; s++) {
p_push_constant.atlas_slice = s;
@ -958,7 +958,7 @@ LightmapperRD::BakeError LightmapperRD::_denoise(RenderingDevice *p_rd, Ref<RDSh
p_rd->compute_list_bind_uniform_set(compute_list, p_compute_base_uniform_set, 0);
p_rd->compute_list_bind_uniform_set(compute_list, denoise_uniform_set, 1);
p_rd->compute_list_set_push_constant(compute_list, &p_push_constant, sizeof(PushConstant));
p_rd->compute_list_dispatch(compute_list, (w - 1) / 8 + 1, (h - 1) / 8 + 1, 1);
p_rd->compute_list_dispatch(compute_list, Math::division_round_up(w, 8), Math::division_round_up(h, 8), 1);
p_rd->compute_list_end();
p_rd->submit();
@ -1399,7 +1399,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
rd->free(compute_shader_secondary); \
rd->free(compute_shader_light_probes);
Vector3i group_size((atlas_size.x - 1) / 8 + 1, (atlas_size.y - 1) / 8 + 1, 1);
Vector3i group_size(Math::division_round_up(atlas_size.x, 8), Math::division_round_up(atlas_size.y, 8), 1);
rd->submit();
rd->sync();
@ -1592,10 +1592,10 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
int max_region_size = nearest_power_of_2_templated(int(GLOBAL_GET("rendering/lightmapping/bake_performance/region_size")));
int max_rays = GLOBAL_GET("rendering/lightmapping/bake_performance/max_rays_per_pass");
int x_regions = (atlas_size.width - 1) / max_region_size + 1;
int y_regions = (atlas_size.height - 1) / max_region_size + 1;
int x_regions = Math::division_round_up(atlas_size.width, max_region_size);
int y_regions = Math::division_round_up(atlas_size.height, max_region_size);
int ray_iterations = (push_constant.ray_count - 1) / max_rays + 1;
int ray_iterations = Math::division_round_up((int32_t)push_constant.ray_count, max_rays);
rd->submit();
rd->sync();
@ -1614,7 +1614,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
push_constant.region_ofs[0] = x;
push_constant.region_ofs[1] = y;
group_size = Vector3i((w - 1) / 8 + 1, (h - 1) / 8 + 1, 1);
group_size = Vector3i(Math::division_round_up(w, 8), Math::division_round_up(h, 8), 1);
for (int k = 0; k < ray_iterations; k++) {
RD::ComputeListID compute_list = rd->compute_list_begin();
@ -1700,7 +1700,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
push_constant.probe_count = probe_positions.size();
int max_rays = GLOBAL_GET("rendering/lightmapping/bake_performance/max_rays_per_probe_pass");
int ray_iterations = (push_constant.ray_count - 1) / max_rays + 1;
int ray_iterations = Math::division_round_up((int32_t)push_constant.ray_count, max_rays);
for (int i = 0; i < ray_iterations; i++) {
RD::ComputeListID compute_list = rd->compute_list_begin();
@ -1711,7 +1711,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
push_constant.ray_from = i * max_rays;
push_constant.ray_to = MIN((i + 1) * max_rays, int32_t(push_constant.ray_count));
rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(PushConstant));
rd->compute_list_dispatch(compute_list, (probe_positions.size() - 1) / 64 + 1, 1, 1);
rd->compute_list_dispatch(compute_list, Math::division_round_up(probe_positions.size(), 64), 1, 1);
rd->compute_list_end(); //done
rd->submit();

View file

@ -39,7 +39,7 @@ void BitMap::create(const Size2i &p_size) {
ERR_FAIL_COND(static_cast<int64_t>(p_size.width) * static_cast<int64_t>(p_size.height) > INT32_MAX);
Error err = bitmask.resize((((p_size.width * p_size.height) - 1) / 8) + 1);
Error err = bitmask.resize(Math::division_round_up(p_size.width * p_size.height, 8));
ERR_FAIL_COND(err != OK);
width = p_size.width;

View file

@ -266,8 +266,8 @@ void ClusterBuilderRD::setup(Size2i p_screen_size, uint32_t p_max_elements, RID
screen_size = p_screen_size;
cluster_screen_size.width = (p_screen_size.width - 1) / cluster_size + 1;
cluster_screen_size.height = (p_screen_size.height - 1) / cluster_size + 1;
cluster_screen_size.width = Math::division_round_up((uint32_t)p_screen_size.width, cluster_size);
cluster_screen_size.height = Math::division_round_up((uint32_t)p_screen_size.height, cluster_size);
max_elements_by_type = p_max_elements;
if (max_elements_by_type % 32) { // Needs to be aligned to 32.
@ -503,7 +503,8 @@ void ClusterBuilderRD::bake_cluster() {
push_constant.max_render_element_count_div_32 = render_element_max / 32;
push_constant.cluster_screen_size[0] = cluster_screen_size.x;
push_constant.cluster_screen_size[1] = cluster_screen_size.y;
push_constant.render_element_count_div_32 = render_element_count > 0 ? (render_element_count - 1) / 32 + 1 : 0;
push_constant.render_element_count_div_32 = Math::division_round_up(render_element_count, 32U);
push_constant.max_cluster_element_count_div_32 = max_elements_by_type / 32;
push_constant.pad1 = 0;
push_constant.pad2 = 0;

View file

@ -1050,8 +1050,8 @@ void CopyEffects::cubemap_downsample(RID p_source_cubemap, RID p_dest_cubemap, c
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 0, u_source_cubemap), 0);
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 1, u_dest_cubemap), 1);
int x_groups = (p_size.x - 1) / 8 + 1;
int y_groups = (p_size.y - 1) / 8 + 1;
int x_groups = Math::division_round_up(p_size.x, 8);
int y_groups = Math::division_round_up(p_size.y, 8);
RD::get_singleton()->compute_list_set_push_constant(compute_list, &cubemap_downsampler.push_constant, sizeof(CubemapDownsamplerPushConstant));
@ -1204,8 +1204,8 @@ void CopyEffects::cubemap_roughness(RID p_source_rd_texture, RID p_dest_texture,
RD::get_singleton()->compute_list_set_push_constant(compute_list, &roughness.push_constant, sizeof(CubemapRoughnessPushConstant));
int x_groups = (p_size - 1) / 8 + 1;
int y_groups = (p_size - 1) / 8 + 1;
int x_groups = Math::division_round_up(p_size, 8);
int y_groups = x_groups;
RD::get_singleton()->compute_list_dispatch(compute_list, x_groups, y_groups, p_face_id > 9 ? 6 : 1);

View file

@ -1069,8 +1069,8 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P
uint32_t cluster_size = p_settings.cluster_builder->get_cluster_size();
params.cluster_shift = get_shift_from_power_of_2(cluster_size);
uint32_t cluster_screen_width = (p_settings.rb_size.x - 1) / cluster_size + 1;
uint32_t cluster_screen_height = (p_settings.rb_size.y - 1) / cluster_size + 1;
uint32_t cluster_screen_width = Math::division_round_up((uint32_t)p_settings.rb_size.x, cluster_size);
uint32_t cluster_screen_height = Math::division_round_up((uint32_t)p_settings.rb_size.y, cluster_size);
params.max_cluster_element_count_div_32 = p_settings.max_cluster_elements / 32;
params.cluster_type_size = cluster_screen_width * cluster_screen_height * (params.max_cluster_element_count_div_32 + 32);
params.cluster_width = cluster_screen_width;

View file

@ -3143,7 +3143,7 @@ void GI::VoxelGIInstance::update(bool p_update_light_instances, const Vector<RID
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, gi->voxel_gi_lighting_shader_version_pipelines[VOXEL_GI_SHADER_VERSION_DYNAMIC_OBJECT_LIGHTING]);
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, dynamic_maps[0].uniform_set, 0);
RD::get_singleton()->compute_list_set_push_constant(compute_list, &push_constant, sizeof(VoxelGIDynamicPushConstant));
RD::get_singleton()->compute_list_dispatch(compute_list, (rect.size.x - 1) / 8 + 1, (rect.size.y - 1) / 8 + 1, 1);
RD::get_singleton()->compute_list_dispatch(compute_list, Math::division_round_up(rect.size.x, 8), Math::division_round_up(rect.size.y, 8), 1);
//print_line("rect: " + itos(i) + ": " + rect);
for (int k = 1; k < dynamic_maps.size(); k++) {
@ -3205,7 +3205,7 @@ void GI::VoxelGIInstance::update(bool p_update_light_instances, const Vector<RID
}
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, dynamic_maps[k].uniform_set, 0);
RD::get_singleton()->compute_list_set_push_constant(compute_list, &push_constant, sizeof(VoxelGIDynamicPushConstant));
RD::get_singleton()->compute_list_dispatch(compute_list, (rect.size.x - 1) / 8 + 1, (rect.size.y - 1) / 8 + 1, 1);
RD::get_singleton()->compute_list_dispatch(compute_list, Math::division_round_up(rect.size.x, 8), Math::division_round_up(rect.size.y, 8), 1);
}
RD::get_singleton()->compute_list_end();

View file

@ -626,8 +626,8 @@ void RenderForwardClustered::_setup_environment(const RenderDataRD *p_render_dat
scene_state.ubo.cluster_shift = get_shift_from_power_of_2(p_render_data->cluster_size);
scene_state.ubo.max_cluster_element_count_div_32 = p_render_data->cluster_max_elements / 32;
{
uint32_t cluster_screen_width = (p_screen_size.width - 1) / p_render_data->cluster_size + 1;
uint32_t cluster_screen_height = (p_screen_size.height - 1) / p_render_data->cluster_size + 1;
uint32_t cluster_screen_width = Math::division_round_up((uint32_t)p_screen_size.width, p_render_data->cluster_size);
uint32_t cluster_screen_height = Math::division_round_up((uint32_t)p_screen_size.height, p_render_data->cluster_size);
scene_state.ubo.cluster_type_size = cluster_screen_width * cluster_screen_height * (scene_state.ubo.max_cluster_element_count_div_32 + 32);
scene_state.ubo.cluster_width = cluster_screen_width;
}

View file

@ -1033,7 +1033,7 @@ void main() {
if (local == ivec3(0) && store_position_count > 0) {
store_from_index = atomicAdd(dispatch_data.total_count, store_position_count);
uint group_count = (store_from_index + store_position_count - 1) / 64 + 1;
uint group_count = Math::division_round_up(store_from_index + store_position_count, 64);
atomicMax(dispatch_data.x, group_count);
}

View file

@ -1553,7 +1553,7 @@ void MeshStorage::_multimesh_make_local(MultiMesh *multimesh) const {
memset(w, 0, buffer_size * sizeof(float));
}
}
uint32_t data_cache_dirty_region_count = (multimesh->instances - 1) / MULTIMESH_DIRTY_REGION_SIZE + 1;
uint32_t data_cache_dirty_region_count = Math::division_round_up(multimesh->instances, MULTIMESH_DIRTY_REGION_SIZE);
multimesh->data_cache_dirty_regions = memnew_arr(bool, data_cache_dirty_region_count);
memset(multimesh->data_cache_dirty_regions, 0, data_cache_dirty_region_count * sizeof(bool));
multimesh->data_cache_dirty_region_count = 0;
@ -1581,7 +1581,7 @@ void MeshStorage::_multimesh_update_motion_vectors_data_cache(MultiMesh *multime
uint32_t current_ofs = multimesh->motion_vectors_current_offset * multimesh->stride_cache * sizeof(float);
uint32_t previous_ofs = multimesh->motion_vectors_previous_offset * multimesh->stride_cache * sizeof(float);
uint32_t visible_instances = multimesh->visible_instances >= 0 ? multimesh->visible_instances : multimesh->instances;
uint32_t visible_region_count = visible_instances == 0 ? 0 : (visible_instances - 1) / MULTIMESH_DIRTY_REGION_SIZE + 1;
uint32_t visible_region_count = visible_instances == 0 ? 0 : Math::division_round_up(visible_instances, (uint32_t)MULTIMESH_DIRTY_REGION_SIZE);
uint32_t region_size = multimesh->stride_cache * MULTIMESH_DIRTY_REGION_SIZE * sizeof(float);
uint32_t size = multimesh->stride_cache * (uint32_t)multimesh->instances * (uint32_t)sizeof(float);
for (uint32_t i = 0; i < visible_region_count; i++) {
@ -1601,7 +1601,7 @@ bool MeshStorage::_multimesh_uses_motion_vectors(MultiMesh *multimesh) {
void MeshStorage::_multimesh_mark_dirty(MultiMesh *multimesh, int p_index, bool p_aabb) {
uint32_t region_index = p_index / MULTIMESH_DIRTY_REGION_SIZE;
#ifdef DEBUG_ENABLED
uint32_t data_cache_dirty_region_count = (multimesh->instances - 1) / MULTIMESH_DIRTY_REGION_SIZE + 1;
uint32_t data_cache_dirty_region_count = Math::division_round_up(multimesh->instances, MULTIMESH_DIRTY_REGION_SIZE);
ERR_FAIL_UNSIGNED_INDEX(region_index, data_cache_dirty_region_count); //bug
#endif
if (!multimesh->data_cache_dirty_regions[region_index]) {
@ -1622,7 +1622,7 @@ void MeshStorage::_multimesh_mark_dirty(MultiMesh *multimesh, int p_index, bool
void MeshStorage::_multimesh_mark_all_dirty(MultiMesh *multimesh, bool p_data, bool p_aabb) {
if (p_data) {
uint32_t data_cache_dirty_region_count = (multimesh->instances - 1) / MULTIMESH_DIRTY_REGION_SIZE + 1;
uint32_t data_cache_dirty_region_count = Math::division_round_up(multimesh->instances, MULTIMESH_DIRTY_REGION_SIZE);
for (uint32_t i = 0; i < data_cache_dirty_region_count; i++) {
if (!multimesh->data_cache_dirty_regions[i]) {
@ -2021,8 +2021,8 @@ void MeshStorage::_update_dirty_multimeshes() {
uint32_t total_dirty_regions = multimesh->data_cache_dirty_region_count + multimesh->previous_data_cache_dirty_region_count;
if (total_dirty_regions != 0) {
uint32_t data_cache_dirty_region_count = (multimesh->instances - 1) / MULTIMESH_DIRTY_REGION_SIZE + 1;
uint32_t visible_region_count = visible_instances == 0 ? 0 : (visible_instances - 1) / MULTIMESH_DIRTY_REGION_SIZE + 1;
uint32_t data_cache_dirty_region_count = Math::division_round_up(multimesh->instances, (int)MULTIMESH_DIRTY_REGION_SIZE);
uint32_t visible_region_count = visible_instances == 0 ? 0 : Math::division_round_up(visible_instances, (uint32_t)MULTIMESH_DIRTY_REGION_SIZE);
uint32_t region_size = multimesh->stride_cache * MULTIMESH_DIRTY_REGION_SIZE * sizeof(float);
if (total_dirty_regions > 32 || total_dirty_regions > visible_region_count / 2) {

View file

@ -5009,7 +5009,7 @@ void RenderingDevice::compute_list_dispatch_threads(ComputeListID p_list, uint32
#endif
compute_list_dispatch(p_list, (p_x_threads - 1) / cl->state.local_group_size[0] + 1, (p_y_threads - 1) / cl->state.local_group_size[1] + 1, (p_z_threads - 1) / cl->state.local_group_size[2] + 1);
compute_list_dispatch(p_list, Math::division_round_up(p_x_threads, cl->state.local_group_size[0]), Math::division_round_up(p_y_threads, cl->state.local_group_size[1]), Math::division_round_up(p_z_threads, cl->state.local_group_size[2]));
}
void RenderingDevice::compute_list_dispatch_indirect(ComputeListID p_list, RID p_buffer, uint32_t p_offset) {

View file

@ -110,6 +110,29 @@ TEST_CASE_TEMPLATE("[Math] round/floor/ceil", T, float, double) {
CHECK(Math::ceil((T)-1.9) == (T)-1.0);
}
TEST_CASE_TEMPLATE("[Math] integer division round up unsigned", T, uint32_t, uint64_t) {
CHECK(Math::division_round_up((T)0, (T)64) == 0);
CHECK(Math::division_round_up((T)1, (T)64) == 1);
CHECK(Math::division_round_up((T)63, (T)64) == 1);
CHECK(Math::division_round_up((T)64, (T)64) == 1);
CHECK(Math::division_round_up((T)65, (T)64) == 2);
CHECK(Math::division_round_up((T)65, (T)1) == 65);
}
TEST_CASE_TEMPLATE("[Math] integer division round up signed", T, int32_t, int64_t) {
CHECK(Math::division_round_up((T)0, (T)64) == 0);
CHECK(Math::division_round_up((T)1, (T)64) == 1);
CHECK(Math::division_round_up((T)63, (T)64) == 1);
CHECK(Math::division_round_up((T)64, (T)64) == 1);
CHECK(Math::division_round_up((T)65, (T)64) == 2);
CHECK(Math::division_round_up((T)65, (T)1) == 65);
CHECK(Math::division_round_up((T)-1, (T)64) == 0);
CHECK(Math::division_round_up((T)-1, (T)-1) == 1);
CHECK(Math::division_round_up((T)-1, (T)1) == -1);
CHECK(Math::division_round_up((T)-1, (T)-2) == 1);
CHECK(Math::division_round_up((T)-4, (T)-2) == 2);
}
TEST_CASE_TEMPLATE("[Math] sin/cos/tan", T, float, double) {
CHECK(Math::sin((T)-0.1) == doctest::Approx((T)-0.0998334166));
CHECK(Math::sin((T)0.1) == doctest::Approx((T)0.0998334166));