Commit 4b5b2c64 authored by Radek Ošlejšek's avatar Radek Ošlejšek
Browse files

Merge branch '317-gpu-distance-unit-test' into 'master'

Added unit test for GPU distance measurement

Closes #317

See merge request grp-fidentis/analyst2!342
parents d3afd5ea f2fe6030
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -73,7 +73,7 @@ public class SsboBufferImpl extends AbstractBuffer implements SsboBuffer {
        gl.glNamedBufferData(getGlName(), items * getItemSize() + extraSpace, null, getUsage());

        if (reset) {
            gl.glClearNamedBufferData(getGlName(), GL_R8, GL_RED, GL_UNSIGNED_BYTE, null); // zero buffer
            gl.glClearNamedBufferData(getGlName(), GL_R32F, GL_RED, GL_FLOAT, null); // zero buffer
        }
    }

+1 −1
Original line number Diff line number Diff line
@@ -36,7 +36,7 @@ ivec3 locationToCell(vec3 loc) {
    Also estimates the optional cell size for the grid using heuristic.

    The min/max bounding corners and sum of the edge distances are computed using parallel reduction.
    This implementation works in-place and for arbitrary triangle array size.
    This implementation works in-place and for arbitrary triangle array size >= 2.
    This shader should be dispatched in multiple rounds and with work group size reduced with the half of the previous size.
    On last single invocation, results will be written to the grid info buffer.

+2 −2
Original line number Diff line number Diff line
@@ -192,8 +192,8 @@ public class RcDistanceGPUBenchmark {
    private static class MeasuredTimes {

        // these times are stored in nanosecond precision
        List<Long> buildTimes = new ArrayList<>();
        List<Long> visitTimes = new ArrayList<>();
        private List<Long> buildTimes = new ArrayList<>();
        private List<Long> visitTimes = new ArrayList<>();

        public long getTotalBuildTimeMs() {
            return buildTimes.stream().reduce(Long::sum).get() / 1000_000; // divide by 1000_000 to get a millis
+6 −1
Original line number Diff line number Diff line
@@ -46,7 +46,7 @@ public class MeshDistanceRCGPU extends MeshDistanceVisitorImpl {
     * Creates 3D uniform grid and populates it with triangles from facets.
     *
     * @param context active OpenGL context on which makeCurrent() can be called now and during visit
     * @param mainFacets facets from which to take triangles
     * @param mainFacets facets from which to take triangles, at least two triangles must be present in total
     * @param relativeDistance If true, then the visitor calculates the relative distances with respect
     *                         to the normal vectors of source facets (normal vectors have to present),
     *                         i.e., we can get negative distances.
@@ -64,6 +64,10 @@ public class MeshDistanceRCGPU extends MeshDistanceVisitorImpl {
        int trianglesCount = mainFacets.stream().map(MeshFacet::getNumTriangles).reduce(Integer::sum).get();
        int verticesCount = mainFacets.stream().map(MeshFacet::getNumberOfVertices).reduce(Integer::sum).get();

        if(trianglesCount < 2) {
            throw new IllegalArgumentException("This GPU distance measurement method needs at least two triangles.");
        }

        fillBufferWithVertices(mainFacets, verticesCount);

        fillBufferWithTriangles(mainFacets, trianglesCount);
@@ -194,6 +198,7 @@ public class MeshDistanceRCGPU extends MeshDistanceVisitorImpl {
                case GRID_PARAMETERS_GLSL_PROGRAM -> program.setNumberVar("invocations", invocations);
                // tells the shader the reduction direction upsweep/downsweep
                case GRID_CELL_BOUNDS_GLSL_PROGRAM -> program.setNumberVar("upsweep", reverse ? GL_FALSE : GL_TRUE);
                default -> {}
            }

            GlslServices.runProgram(context, (invocations / WORKGROUP_SIZE) + 1, 1, 1);
+124 −0
Original line number Diff line number Diff line
package cz.fidentis.analyst.engines.distance;

import com.jogamp.opengl.*;
import cz.fidentis.analyst.data.mesh.MeshFacet;
import cz.fidentis.analyst.data.mesh.MeshFactory;
import cz.fidentis.analyst.data.mesh.impl.cornertable.CornerTableRow;
import cz.fidentis.analyst.engines.distance.measurement.FacetDistances;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIf;

import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;

import static org.junit.jupiter.api.Assertions.assertTrue;

/**
 * Tests GPU distance measurement. Runs only when GL capable window manager is present.
 * Results are compared to reference CPU implementation with defined error - note floats vs doubles.
 *
 * @author Pavol Kycina
 */
public class MeshDistanceRCGPUTest {

    /** this tells how much the GPU calculated distance can be different from CPU distance (note GPU floats vs CPU doubles)*/
    private static final double ALLOWED_DISTANCE_ERROR = 0.01;

    protected static MeshFacet getTrivialTwoTriangleFacet(double offsetZ, double size) {
        MeshFacet facet = MeshFactory.createEmptyMeshFacet();

        // these 6 points create one square with two triangles, no vertex sharing
        facet.addVertex(MeshFactory.createMeshPoint(new Point3d(-size, -size, offsetZ), new Vector3d(0, 0, 1), new Vector3d(), null));
        facet.addVertex(MeshFactory.createMeshPoint(new Point3d(size, -size, offsetZ), new Vector3d(0, 0, 1), new Vector3d(), null));
        facet.addVertex(MeshFactory.createMeshPoint(new Point3d(-size, size, offsetZ), new Vector3d(0, 0, 1), new Vector3d(), null));

        facet.addVertex(MeshFactory.createMeshPoint(new Point3d(-size, size, offsetZ), new Vector3d(0, 0, 1), new Vector3d(), null));
        facet.addVertex(MeshFactory.createMeshPoint(new Point3d(size, -size, offsetZ), new Vector3d(0, 0, 1), new Vector3d(), null));
        facet.addVertex(MeshFactory.createMeshPoint(new Point3d(-size, size, offsetZ), new Vector3d(0, 0, 1), new Vector3d(), null));

        facet.getCornerTable().addRow(new CornerTableRow(0, -1));
        facet.getCornerTable().addRow(new CornerTableRow(1, -1));
        facet.getCornerTable().addRow(new CornerTableRow(2, -1));

        facet.getCornerTable().addRow(new CornerTableRow(3, -1));
        facet.getCornerTable().addRow(new CornerTableRow(4, -1));
        facet.getCornerTable().addRow(new CornerTableRow(5, -1));

        return facet;
    }

    @Test
    @EnabledIf("isOpenGLAvailable")
    public void gpuDistanceTest() {
        GLAutoDrawable drawable = createDummyGLContext();
        GLContext context = drawable.getContext();

        // idea is that rays are shot from the smaller two triangles to the bigger two triangles ib the background
        // which should definitely result into intersections
        MeshFacet smallFacet = getTrivialTwoTriangleFacet(0, 1);
        MeshFacet normalFacet = getTrivialTwoTriangleFacet(1, 10);

        // CPU visit
        MeshDistanceVisitor cpuVisitor = new MeshDistanceConfig(MeshDistanceConfig.Method.RAY_CASTING, normalFacet, null, true, true).getVisitor();
        smallFacet.accept(cpuVisitor);
        FacetDistances referenceDistances = cpuVisitor.getDistancesOfVisitedFacets().getFacetMeasurement(smallFacet);

        // GPU visit
        MeshDistanceVisitor gpuVisitor = new MeshDistanceConfig(MeshDistanceConfig.Method.RAY_CASTING_GPU, normalFacet, context, true, true).getVisitor();
        smallFacet.accept(gpuVisitor);
        FacetDistances gpuDistances = gpuVisitor.getDistancesOfVisitedFacets().getFacetMeasurement(smallFacet);

        assertTrue(areEqual(referenceDistances, gpuDistances));

        gpuVisitor.dispose();
        drawable.destroy();
    }

    /**
     * Tests two distance measurements if they have the same values with defined error.
     * @param cpuDistances first values
     * @param gpuDistances second values
     * @return true if they are the same (with error), false otherwise
     */
    private static boolean areEqual(FacetDistances cpuDistances, FacetDistances gpuDistances) {
        if(cpuDistances.size() != gpuDistances.size()) {
            return false;
        }

        int matches = 0;
        for (int i = 0; i < cpuDistances.size(); i++) {
            double dGPU = cpuDistances.get(i).getDistance();
            double dCPU = gpuDistances.get(i).getDistance();

            // round the numbers because GPU uses floats
            if((Double.isInfinite(dGPU) && Double.isInfinite(dCPU)) || Math.abs(dGPU - dCPU) < ALLOWED_DISTANCE_ERROR) {
                matches++;
            }else {
                // uncomment for debugging
                //System.out.println("NOT MATCHING: (gpu) " + dGPU + " (cpu) " + dCPU + " i: " + i);
            }
        }
        return cpuDistances.size() == matches;
    }

    /**
     * Creates dummy GL drawable with its context. Available only on platforms with window manager.
     * @return initialized GL context
     */
    private static GLAutoDrawable createDummyGLContext() {
        GLProfile glProfile = GLProfile.getDefault();
        GLCapabilities glCapabilities = new GLCapabilities(glProfile);
        GLAutoDrawable drawable = GLDrawableFactory.getFactory(glProfile).createOffscreenAutoDrawable(null, glCapabilities, null, 1, 1);
        drawable.display(); // initialize context
        return drawable;
    }

    private static boolean isOpenGLAvailable() {
        try {
            GLProfile glProfile = GLProfile.getDefault();
            return glProfile != null;
        }catch (java.lang.UnsatisfiedLinkError ignored) {
            return false;
        }
    }
}
Loading