All Downloads are FREE. Search and download functionalities are using the official Maven repository.

se.llbit.chunky.renderer.RenderWorker Maven / Gradle / Ivy

There is a newer version: 1.4.5
Show newest version
/* Copyright (c) 2012 Jesper Öqvist 
 *
 * This file is part of Chunky.
 *
 * Chunky is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Chunky is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with Chunky.  If not, see .
 */
package se.llbit.chunky.renderer;

import se.llbit.chunky.renderer.scene.Camera;
import se.llbit.chunky.renderer.scene.RayTracer;
import se.llbit.chunky.renderer.scene.Scene;
import se.llbit.log.Log;
import se.llbit.math.QuickMath;
import se.llbit.math.Ray;

import java.util.Random;

/**
 * Performs rendering work.
 *
 * @author Jesper Öqvist 
 */
public class RenderWorker extends Thread {

  /**
   * Sleep interval (in ns)
   */
  private static final int SLEEP_INTERVAL = 75000000;

  private final int id;
  private final AbstractRenderManager manager;

  private final WorkerState state;
  private final RayTracer previewRayTracer;
  private final RayTracer rayTracer;
  private long jobTime = 0;

  /**
   * Create a new render worker, slave to a given render manager.
   *
   * @param manager the parent render manager
   * @param id the ID for this worker
   * @param seed the random generator seed
   */
  public RenderWorker(AbstractRenderManager manager, int id, long seed) {
    super("3D Render Worker " + id);

    this.manager = manager;
    this.previewRayTracer = manager.getPreviewRayTracer();
    this.rayTracer = manager.getRayTracer();
    this.id = id;
    state = new WorkerState();
    state.random = new Random(seed);
    state.ray = new Ray();
  }

  @Override public void run() {
    try {
      try {
        while (!isInterrupted()) {
          work(manager.getNextJob());
          manager.jobDone();
        }
      } catch (InterruptedException ignored) {
        // Interrupted.
      }
    } catch (Throwable e) {
      Log.error("Render worker " + id +
          " crashed with uncaught exception.", e);
    }
  }

  /**
   * Perform the rendering work for a single render job.
   *
   * @throws InterruptedException interrupted while sleeping
   */
  private void work(int jobId) throws InterruptedException {

    Scene scene = manager.getBufferedScene();

    Random random = state.random;
    Ray ray = state.ray;

    int width = scene.canvasWidth();
    int height = scene.canvasHeight();

    double halfWidth = width / (2.0 * height);
    double invHeight = 1.0 / height;

    // Calculate pixel bounds for this job.
    int xjobs = (width + (manager.tileWidth - 1)) / manager.tileWidth;
    int x0 = manager.tileWidth * (jobId % xjobs);
    int x1 = Math.min(x0 + manager.tileWidth, width);
    int y0 = manager.tileWidth * (jobId / xjobs);
    int y1 = Math.min(y0 + manager.tileWidth, height);

    double[] samples = scene.getSampleBuffer();
    final Camera cam = scene.camera();

    long jobStart = System.nanoTime();

    if (scene.getMode() != RenderMode.PREVIEW) {
      for (int y = y0; y < y1; ++y) {
        int offset = y * width * 3 + x0 * 3;
        for (int x = x0; x < x1; ++x) {

          double sr = 0;
          double sg = 0;
          double sb = 0;

          for (int i = 0; i < RenderConstants.SPP_PER_PASS; ++i) {
            double oy = random.nextDouble();
            double ox = random.nextDouble();

            cam.calcViewRay(ray, random, (-halfWidth + (x + ox) * invHeight),
                (-.5 + (y + oy) * invHeight));

            scene.rayTrace(rayTracer, state);

            sr += ray.color.x;
            sg += ray.color.y;
            sb += ray.color.z;
          }
          double sinv = 1.0 / (scene.spp + RenderConstants.SPP_PER_PASS);
          samples[offset + 0] = (samples[offset + 0] * scene.spp + sr) * sinv;
          samples[offset + 1] = (samples[offset + 1] * scene.spp + sg) * sinv;
          samples[offset + 2] = (samples[offset + 2] * scene.spp + sb) * sinv;

          if (scene.shouldFinalizeBuffer()) {
            scene.finalizePixel(x, y);
          }

          offset += 3;
        }
      }

    } else {
      // Preview rendering.
      Ray target = new Ray(ray);
      boolean hit = scene.trace(target);
      int tx = (int) QuickMath.floor(target.o.x + target.d.x * Ray.OFFSET);
      int ty = (int) QuickMath.floor(target.o.y + target.d.y * Ray.OFFSET);
      int tz = (int) QuickMath.floor(target.o.z + target.d.z * Ray.OFFSET);

      for (int x = x0; x < x1; ++x)
        for (int y = y0; y < y1; ++y) {

          boolean firstFrame = scene.previewCount > 1;
          if (firstFrame) {
            if (((x + y) % 2) == 0) {
              continue;
            }
          } else {
            if (((x + y) % 2) != 0) {
              scene.finalizePixel(x, y);
              continue;
            }
          }

          // Draw the crosshairs.
          if (x == width / 2 && (y >= height / 2 - 5 && y <= height / 2 + 5) || y == height / 2 && (
              x >= width / 2 - 5 && x <= width / 2 + 5)) {
            samples[(y * width + x) * 3 + 0] = 0xFF;
            samples[(y * width + x) * 3 + 1] = 0xFF;
            samples[(y * width + x) * 3 + 2] = 0xFF;
            scene.finalizePixel(x, y);
            continue;
          }

          cam.calcViewRay(ray, random, (-halfWidth + (double) x * invHeight),
              (-.5 + (double) y * invHeight));

          scene.rayTrace(previewRayTracer, state);

          // Target highlighting.
          int rx = (int) QuickMath.floor(ray.o.x + ray.d.x * Ray.OFFSET);
          int ry = (int) QuickMath.floor(ray.o.y + ray.d.y * Ray.OFFSET);
          int rz = (int) QuickMath.floor(ray.o.z + ray.d.z * Ray.OFFSET);
          if (hit && tx == rx && ty == ry && tz == rz) {
            ray.color.x = 1 - ray.color.x;
            ray.color.y = 1 - ray.color.y;
            ray.color.z = 1 - ray.color.z;
            ray.color.w = 1;
          }

          samples[(y * width + x) * 3 + 0] = ray.color.x;
          samples[(y * width + x) * 3 + 1] = ray.color.y;
          samples[(y * width + x) * 3 + 2] = ray.color.z;

          scene.finalizePixel(x, y);

          if (firstFrame) {
            if (y % 2 == 0 && x < (width - 1)) {
              // Copy the current pixel to the next one.
              scene.copyPixel(y * width + x, 1);
            } else if (y % 2 != 0 && x > 0) {
              // Copy the next pixel to this pixel.
              scene.copyPixel(y * width + x, -1);
            }
          }

        }
    }
    jobTime += System.nanoTime() - jobStart;
    if (jobTime > SLEEP_INTERVAL) {
      if (manager.cpuLoad < 100) {
        // sleep = jobTime * (1-utilization) / utilization
        double load = (100.0 - manager.cpuLoad) / manager.cpuLoad;
        sleep((long) ((jobTime / 1000000.0) * load));
      }
      jobTime = 0;
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy