diff --git a/documents/references.md b/documents/references.md index 6766a310513366055ebdb9464967c82d6b6f8cd5..e67f5be24437497c162ed4fe737da6756ca71795 100644 --- a/documents/references.md +++ b/documents/references.md @@ -29,7 +29,9 @@ Various references used in project. ### Other #### C++ Strings * What is a C++ string - <https://stackoverflow.com/a/3535904> -* String splitting - <https://stackoverflow.com/a/14266139> +* String splitting: +** <https://stackoverflow.com/a/14266139> +** <https://www.cplusplus.com/reference/string/string/substr/> * Getting File/LineNum: ** <https://stackoverflow.com/a/42135375> ** <https://stackoverflow.com/a/8884408> diff --git a/part_0/image.cu b/part_0/image.cu new file mode 100644 index 0000000000000000000000000000000000000000..874b1ea1261007b482fb195014ebc5d6a39761f8 --- /dev/null +++ b/part_0/image.cu @@ -0,0 +1,729 @@ +/** + * File for image handling logic. + */ + + +// User Import Headers. +#ifndef user_headers + #define user_headers + #include "image.h" + #include "utility.h" +#endif + +#ifndef user_headers_logging + #define user_headers_logging + #include "logging.h" +#endif + + +// Global Variables. +#ifndef LOGGING_CLASS_INIT + #define LOGGING_CLASS_INIT + extern Logging logger; +#endif +const Image::PixelStruct Image::pixelBlack = Image::PixelStruct(0); +const Image::PixelStruct Image::pixelWhite = Image::PixelStruct(1); +const Image::PixelStruct Image::pixelRed = Image::PixelStruct(1,0,0); +const Image::PixelStruct Image::pixelGreen = Image::PixelStruct(0,1,0); +const Image::PixelStruct Image::pixelBlue = Image::PixelStruct(0,0,1); +const Image::PixelStruct Image::pixelYellow = Image::PixelStruct(1,1,0); +const Image::PixelStruct Image::pixelPink = Image::PixelStruct(1,0,1); +const Image::PixelStruct Image::pixelLiBlue = Image::PixelStruct(0,1,1); + + +//region CUDA Methods. + +/** + * Displays various properties of all CUDA devices found on machine. + **/ +void display_cuda_device_properties() { + // Check available CUDA devices. + cudaDeviceProp cuda_device_properties; + int cuda_device_count; + + cudaGetDeviceCount(&cuda_device_count); + + for (int index = 0; index < cuda_device_count; index++) { + cudaGetDeviceProperties(&cuda_device_properties, index); + + printf("\n\n\n\n"); + printf("==== General Information for CUDA Device %d ====\n", index); + printf("Name: %s\n", cuda_device_properties.name); + printf("Compute Capabilities: %d.%d\n", cuda_device_properties.major, cuda_device_properties.minor); + printf("Clock Rate: %d\n", cuda_device_properties.clockRate); + printf("\n\n"); + printf("==== Memory Information for CUDA Device %d ====\n", index); + printf("Total Global Memory: %ld\n", cuda_device_properties.totalGlobalMem); + printf("Total Constant Memory: %ld\n", cuda_device_properties.totalConstMem); + printf("Max Memory Pitch: %ld\n", cuda_device_properties.memPitch); + printf("Texture Alignment: %ld\n", cuda_device_properties.textureAlignment); + printf("\n\n"); + printf("==== Multiprocessing Information for CUDA Device %d ====\n", index); + printf("Multiprocessor Count: %d\n", cuda_device_properties.multiProcessorCount); + printf("Shared Memory Per MP: %ld\n", cuda_device_properties.sharedMemPerBlock); + printf("Registers Per MP: %d\n", cuda_device_properties.regsPerBlock); + printf("Threads in Warp: %d\n", cuda_device_properties.warpSize); + printf("Max Threads Per Block: %d\n", cuda_device_properties.maxThreadsPerBlock); + printf("Max Thread Dimensions: (%d, %d, %d)\n", + cuda_device_properties.maxThreadsDim[0], + cuda_device_properties.maxThreadsDim[1], + cuda_device_properties.maxThreadsDim[2] + ); + printf("Max Grid Dimensions: (%d, %d, %d)\n", + cuda_device_properties.maxGridSize[0], + cuda_device_properties.maxGridSize[1], + cuda_device_properties.maxGridSize[2] + ); + printf("\n\n\n\n"); + } +} + + +/** + * Updates image so that every other pixel alternates between pure black and pure white. + * Purely for testing purposes. + */ +// __global__ void cuda_pixel_alternating(Image::PixelStruct* pixel_arr, int pixel_index) { +__global__ void cuda_alternating_pixels(unsigned long total_pixels, unsigned long chunk_size, int chunk_counter, Image::PixelStruct* pixel_arr) { + // Calculate current index. + unsigned long pixel_index = blockIdx.x * blockDim.x + threadIdx.x; + + // Adjust index for current chunk. + pixel_index += (chunk_size * chunk_counter); + + // Only proceed if GPU thread corresponds to a pixel in the image. Ignore all extra threads. + if (pixel_index <= total_pixels) { + + // Manipulate pixel data based on index. + if (pixel_index % 2 == 0) { + // Set to black. + pixel_arr[pixel_index].r = 0.f; + pixel_arr[pixel_index].g = 0.f; + pixel_arr[pixel_index].b = 0.f; + } else { + // Set to white. + pixel_arr[pixel_index].r = 255.f; + pixel_arr[pixel_index].g = 255.f; + pixel_arr[pixel_index].b = 255.f; + } + } +} + + +/** + * Updates image so that blocks of pixels alternate between pure black and pure white. + * Effectively, it's similar to cuda_alternating_pixels, except that all image sizes should look roughly the same. + * Purely for testing purposes. + */ +// __global__ void cuda_pixel_alternating(Image::PixelStruct* pixel_arr, int pixel_index) { +__global__ void cuda_alternating_blocks(unsigned long total_pixels, unsigned long chunk_size, int chunk_counter, int pixel_block_size, int image_width, int image_height, Image::PixelStruct* pixel_arr) { + // Calculate current index. + unsigned long pixel_index = blockIdx.x * blockDim.x + threadIdx.x; + + // Adjust index for current chunk. + pixel_index += (chunk_size * chunk_counter); + + // Only proceed if GPU thread corresponds to a pixel in the image. Ignore all extra threads. + if (pixel_index <= total_pixels) { + + // Calculate if falls under black or white pixel block. + int row = pixel_index / image_width; + int col = pixel_index % image_width; + int row_grouping = row / pixel_block_size; + int col_grouping = col / pixel_block_size; + float color; + if (row_grouping % 2 == 0) { + if ((col_grouping % 2) == 0) { + color = 255.f; + } else { + color = 0.f; + } + } else { + if ((col_grouping % 2) == 0) { + color = 0.f; + } else { + + color = 255.f; + } + } + + // Set pixel color. + pixel_arr[pixel_index].r = color; + pixel_arr[pixel_index].g = color; + pixel_arr[pixel_index].b = color; + } +} + + +/** + * Updates image to increase percieved value of red in image. + * Purely for testing purposes. + */ +__global__ void cuda_increase_red(unsigned long total_pixels, unsigned long chunk_size, int chunk_counter, Image::PixelStruct* pixel_arr) { + // Calculate current index. + unsigned long pixel_index = blockIdx.x * blockDim.x + threadIdx.x; + + // Adjust index for current chunk. + pixel_index += (chunk_size * chunk_counter); + + // Only proceed if GPU thread corresponds to a pixel in the image. Ignore all extra threads. + if (pixel_index <= total_pixels) { + + // Calculate new pixel colors. + float red = pixel_arr[pixel_index].r + 0.1f; + float green = pixel_arr[pixel_index].g - 0.1f; + float blue = pixel_arr[pixel_index].b - 0.1f; + + // Handle if colors exceed expected values. + if (red > 1.0f) { + red = 1.f; + } + if (green < 0.0f) { + green = 0.f; + } + if (blue < 0.0f) { + blue = 0.f; + } + + // Update pixel values. + pixel_arr[pixel_index].r = red; + pixel_arr[pixel_index].g = green; + pixel_arr[pixel_index].b = blue; + } +} + + +/** + * Updates image to increase percieved value of green in image. + * Purely for testing purposes. + */ +__global__ void cuda_increase_green(unsigned long total_pixels, unsigned long chunk_size, int chunk_counter, Image::PixelStruct* pixel_arr) { + // Calculate current index. + unsigned long pixel_index = blockIdx.x * blockDim.x + threadIdx.x; + + // Adjust index for current chunk. + pixel_index += (chunk_size * chunk_counter); + + // Only proceed if GPU thread corresponds to a pixel in the image. Ignore all extra threads. + if (pixel_index <= total_pixels) { + + // Calculate new pixel colors. + float red = pixel_arr[pixel_index].r - 0.1f; + float green = pixel_arr[pixel_index].g + 0.1f; + float blue = pixel_arr[pixel_index].b - 0.1f; + + // Handle if colors exceed expected values. + if (red < 0.0f) { + red = 0.f; + } + if (green > 1.0f) { + green = 1.f; + } + if (blue < 0.0f) { + blue = 0.f; + } + + // Update pixel values. + pixel_arr[pixel_index].r = red; + pixel_arr[pixel_index].g = green; + pixel_arr[pixel_index].b = blue; + } +} + + +/** + * Updates image to increase percieved value of blue in image. + * Purely for testing purposes. + */ +__global__ void cuda_increase_blue(unsigned long total_pixels, unsigned long chunk_size, int chunk_counter, Image::PixelStruct* pixel_arr) { + // Calculate current index. + unsigned long pixel_index = blockIdx.x * blockDim.x + threadIdx.x; + + // Adjust index for current chunk. + pixel_index += (chunk_size * chunk_counter); + + // Only proceed if GPU thread corresponds to a pixel in the image. Ignore all extra threads. + if (pixel_index <= total_pixels) { + + // Calculate new pixel colors. + float red = pixel_arr[pixel_index].r - 0.1f; + float green = pixel_arr[pixel_index].g - 0.1f; + float blue = pixel_arr[pixel_index].b + 0.1f; + + // Handle if colors exceed expected values. + if (red < 0.0f) { + red = 0.f; + } + if (green < 0.0f) { + green = 0.f; + } + if (blue > 1.0f) { + blue = 1.f; + } + + // Update pixel values. + pixel_arr[pixel_index].r = red; + pixel_arr[pixel_index].g = green; + pixel_arr[pixel_index].b = blue; + } +} + +//endregion CUDA Methods. + + +//region Constructors. + +/** + * Base constructor. + */ +Image::Image() { + logger.debug("Image::Image Constructor():"); + logger.debug(""); + + // Currently test logic. + + // Essentially creates a 10x10 pixel test image. + input_path = "../input/pbm/400x300/hallway_00.pbm"; + output_path = "../output/test_00.pbm"; + width = 10; + height = 10; + bit_depth = 255; + PixelStruct c = pixelBlack; // Default color. Can select based on below preset colors. + + // Create and populate image. + pixel_arr = new PixelStruct[width * height]; + for (int index = 0; index < (width * height); index++) { + pixel_arr[index] = c; + } +} + + +/** + * Constructor that takes input path to image file. + * Output path is determined based on parsing input path. + */ +Image::Image(std::string user_path) { + logger.debug("Image::Image Constructor():"); + logger.debug(" user_path: " + user_path); + logger.debug(""); + + logger.info("Constructing image class."); + + // Re-validate path for saving to class. + path_validator_struct* return_struct = validate_path(user_path); + int path_type = return_struct->file_type; + if (return_struct->err_code) { + logger.error("Error verifying file type: " + return_struct->err_code.message()); + return; + } + free_path_validator_struct(return_struct); + if (path_type == TYPE_FILE) { + input_path = user_path; + calculate_paths(user_path); + import_image_file(); + } else { + logger.error("Provided path is not an image file. Class initialization failed."); + return; + } + + logger.info("Image class created."); +} + + +/** + * Deconstructor. + */ +Image::~Image() { + logger.debug("Deconstructing image class"); + + // Deallocate memory objects. + delete[] pixel_arr; +} + +//endregion Constructors. + + +//region Private Methods. + +/** + * Calculates class input_path and output_path values. + * + * :param image_path: Path to an image on the system. + */ +void Image::calculate_paths(std::string image_path) { + logger.debug("Image::calculate_paths():"); + logger.debug(" image_path: " + image_path); + logger.debug(""); + + // // Calculate output path location. + output_path = "../output/"; + std::string base_filename = get_file_name(); + + // Grab directory substrings and generate full output path. + size_t pos = 0; + std::string token; + while ((pos = image_path.find("/")) != std::string::npos) { + + // Pull current substring value from input path. + token = image_path.substr(0, image_path.find("/")); + image_path.erase(0, pos + 1); + + // Check if directory value is value we care about. + if ( (token != ".") && (token != "..") && (token != "input") ) { + output_path += token + "/"; + } + + // Check if directory exists. + struct stat info; + if ( (stat(output_path.c_str(), &info) != 0) || (! info.st_mode & S_IFDIR)) { + // Directory does not exist. + mkdir(output_path.c_str(), 0777); + } + + } + + output_path += base_filename; +} + + +/** + * Imports image file data, using class variable data. + */ +void Image::import_image_file() { + logger.debug("Image::import_image_file():"); + logger.debug(""); + + logger.info("Attempting to import image data..."); + logger.info("Data from: \"" + input_path + "\""); + + // Open input file. + std::ifstream input_file; + input_file.open(input_path, std::ios::binary | std::ios::in); + + if (! input_file.good()) { + // Failed to open file. + logger.error("Failed to open file \"" + input_path + "\""); + } else { + // File opened. Grab data. + std::string header_format; + input_file >> header_format; + + // Check image data format type. + if (strcmp(header_format.c_str(), "P6") == 0) { + // P6 indicates standard "binary RGB format". + logger.debug("Image is binary RGB format."); + + } else if (strcmp(header_format.c_str(), "P5") == 0) { + // P5 indicates standard "binary greyscale format". + logger.debug("Image is binary greyscale format."); + + } else { + // throw("Can't read input file"); + logger.error(""); + logger.error("Image: \"" + input_path + "\""); + logger.error("Unhandled image format type: " + header_format); + return; + } + + // Save image values to class. + input_file >> width >> height >> bit_depth; + + // Create and populate image. + pixel_arr = new PixelStruct[width * height]; + + // Skip empty lines in necessary until we get to the binary data. + input_file.ignore(256, '\n'); + + // Read pixels based on image format. + if (strcmp(header_format.c_str(), "P6") == 0) { + // Binary RBG format. + // Read each pixel one by one and convert RGB channel bytes to floats. + unsigned char pixel[3]; + for (int index = 0; index < (width * height); ++index) { + input_file.read(reinterpret_cast<char *>(pixel), 3); + pixel_arr[index].r = pixel[0] / 255.f; + pixel_arr[index].g = pixel[1] / 255.f; + pixel_arr[index].b = pixel[2] / 255.f; + } + } else if (strcmp(header_format.c_str(), "P5") == 0) { + // Binary RBG format. + // Read each pixel one by one and convert bytes to floats. + unsigned char* pixel; + pixel = (unsigned char*)calloc(1, sizeof(unsigned char)); + for (int index = 0; index < (width * height); ++index) { + input_file.read(reinterpret_cast<char *>(pixel), 1); + pixel_arr[index].r = *pixel / 255.f; + pixel_arr[index].g = *pixel / 255.f; + pixel_arr[index].b = *pixel / 255.f; + } + free(pixel); + } + } + + input_file.close(); +} + +//endregion Private Methods. + + +//region Public Methods. + +/** + * Displays general properties of image held in class. + */ +void Image::display_properties() { + logger.debug("Image::display_properties():"); + logger.debug(""); + + logger.info(""); + logger.info("input file path: " + input_path); + logger.info("output file path: " + output_path); + logger.info("width: " + std::to_string(width)); + logger.info("height: " + std::to_string(height)); + logger.info("bit depth: " + std::to_string(bit_depth)); + logger.info(""); +} + + +/** + * Saves class image data to output file. + */ +void Image::save() { + logger.debug("Image::save():"); + logger.debug(""); + + logger.debug("Attempting to save image to \"" + output_path + "\"."); + + // Open output file stream. + std::ofstream output_file; + + // Attempt to save file data. + try { + // Attempt to open file. + output_file.open(output_path, std::ios::binary | std::ios::out); + + // Check if opened successfully. + if (output_file.fail()) { + // Failed to open. + logger.error("Failed to open file \"" + output_path + "\""); + } else { + // Opened successfully. + + // Write image "header" data. + output_file << "P6\n" << width << " " << height << "\n255\n"; + + // Write image pixel data. + unsigned char r, g, b; + for (int index = 0; index < (width * height); index++) { + r = static_cast<float>(std::min(1.f, pixel_arr[index].r) * 255); + g = static_cast<float>(std::min(1.f, pixel_arr[index].g) * 255); + b = static_cast<float>(std::min(1.f, pixel_arr[index].b) * 255); + output_file << r << g << b; + } + + } + + // Close file stream. + output_file.close(); + logger.info("Image saved."); + + } catch (const char* err) { + logger.error("Failed to save image: " + std::string(err)); + output_file.close(); + } +} + + +/** + * Gets current file name, using original inpute path value. + */ +std::string Image::get_file_name() { + logger.debug("Image::get_file_name():"); + logger.debug(""); + + return input_path.substr(input_path.find_last_of("/\\") + 1); +} + + +/** + * Changes output file name to provided value. + */ +void Image::set_file_name(std::string file_name) { + logger.debug("Image::compute():"); + logger.debug(" file_name: " + file_name); + logger.debug(""); + + std::string new_path = output_path.substr(0, output_path.find_last_of("/\\") + 1); + output_path = new_path + file_name; +} + + +/** + * Method to actually do work on image. + */ +void Image::compute() { + logger.debug("Image::compute():"); + logger.debug(""); + + // Get file name values. + std::string base_file_name = get_file_name(); + std::string base_file_extension = base_file_name.substr(base_file_name.find_last_of(".") + 1); + base_file_name = base_file_name.substr(0, base_file_name.find_last_of(".")); + + // Get required CUDA device data. + // For now, lazily gets first CUDA device and assumes it's the desired device to use. + // TODO: Handle multiple CUDA devices. + cudaDeviceProp cuda_device_properties; + cudaGetDeviceProperties(&cuda_device_properties, 0); + int cuda_device_max_blocks = cuda_device_properties.maxThreadsDim[0]; + int cuda_device_max_threads = cuda_device_properties.maxThreadsPerBlock; + unsigned long cuda_chunk_size = cuda_device_max_blocks * cuda_device_max_threads; + + logger.info("CUDA Max Blocks: " + std::to_string(cuda_device_max_blocks)); + logger.info("CUDA Max Threads: " + std::to_string(cuda_device_max_threads)); + logger.info("CUDA Chunk Size: " + std::to_string(cuda_chunk_size)); + logger.info("CUDA Global Mem: " + std::to_string(cuda_device_properties.totalGlobalMem)); + + // Calculate some image data. + int pixel_struct_size = sizeof(PixelStruct); + unsigned long image_total_pixels = width * height; + unsigned long pixel_struct_byte_count = image_total_pixels * pixel_struct_size; + + logger.info("Total Image Size: " + std::to_string(image_total_pixels)); + logger.info("Pixel Struct Size: " + std::to_string(pixel_struct_byte_count)); + + // We handle image in chunks, in case we're dealing with large images with more pixels than CUDA device blocks/threads. + int total_image_chunks = (image_total_pixels / cuda_chunk_size) + 1; + logger.info("Total Image Chunks: " + std::to_string(total_image_chunks)); + logger.info("Desired CUDA block count: " + std::to_string(cuda_device_max_blocks)); + logger.info("Desired CUDA thread count: " + std::to_string(cuda_device_max_threads)); + + // Allocate memory on GPU. + PixelStruct* gpu_pixel_arr; + cudaMalloc((void**) &gpu_pixel_arr, pixel_struct_byte_count); + cudaMemcpy(gpu_pixel_arr, pixel_arr, pixel_struct_byte_count, cudaMemcpyHostToDevice); + + // Save original pixel data. + PixelStruct* pixel_arr_orig = new PixelStruct[image_total_pixels]; + for (int index = 0; index < image_total_pixels; index++) { + pixel_arr_orig[index].r = pixel_arr[index].r; + pixel_arr_orig[index].g = pixel_arr[index].g; + pixel_arr_orig[index].b = pixel_arr[index].b; + } + + // Process image by iterating over all chunks for image test. + for (int chunk_index = 0; chunk_index < total_image_chunks; chunk_index++) { + // Run GPU test logic to set alternating pixels to black/white. + set_file_name(base_file_name + "_alternating." + base_file_extension); + cuda_alternating_pixels<<<cuda_device_max_blocks, cuda_device_max_threads>>>(image_total_pixels, cuda_chunk_size, chunk_index, gpu_pixel_arr); + + // Synchronize all GPU computations before iterating over next chunk. + cudaDeviceSynchronize(); + } + + // Save processed image. + cudaMemcpy(pixel_arr, gpu_pixel_arr, pixel_struct_byte_count, cudaMemcpyDeviceToHost); + save(); + + // Reset class pixel array + for (int index = 0; index < image_total_pixels; index++) { + pixel_arr[index].r = pixel_arr_orig[index].r; + pixel_arr[index].g = pixel_arr_orig[index].g; + pixel_arr[index].b = pixel_arr_orig[index].b; + } + cudaMemcpy(gpu_pixel_arr, pixel_arr, pixel_struct_byte_count, cudaMemcpyHostToDevice); + + // Process image by iterating over all chunks for new image test. + for (int chunk_index = 0; chunk_index < total_image_chunks; chunk_index++) { + // Run GPU test logic to create black/white checkerboard pattern. + set_file_name(base_file_name + "_checkerboard." + base_file_extension); + int pixel_block_size = width / 10; + if (pixel_block_size <= 1) { + pixel_block_size = 2; + } + cuda_alternating_blocks<<<cuda_device_max_blocks, cuda_device_max_threads>>>(image_total_pixels, cuda_chunk_size, chunk_index, pixel_block_size, width, height, gpu_pixel_arr); + + // Synchronize all GPU computations before iterating over next chunk. + cudaDeviceSynchronize(); + } + + // Save processed image. + cudaMemcpy(pixel_arr, gpu_pixel_arr, pixel_struct_byte_count, cudaMemcpyDeviceToHost); + save(); + + // Reset class pixel array + for (int index = 0; index < image_total_pixels; index++) { + pixel_arr[index].r = pixel_arr_orig[index].r; + pixel_arr[index].g = pixel_arr_orig[index].g; + pixel_arr[index].b = pixel_arr_orig[index].b; + } + cudaMemcpy(gpu_pixel_arr, pixel_arr, pixel_struct_byte_count, cudaMemcpyHostToDevice); + + // Process image by iterating over all chunks for new image test. + for (int chunk_index = 0; chunk_index < total_image_chunks; chunk_index++) { + // Run GPU test logic to increase/decrease r/g/b colors in image. + set_file_name(base_file_name + "_red." + base_file_extension); + cuda_increase_red<<<cuda_device_max_blocks, cuda_device_max_threads>>>(image_total_pixels, cuda_chunk_size, chunk_index, gpu_pixel_arr); + + // Synchronize all GPU computations before iterating over next chunk. + cudaDeviceSynchronize(); + } + + // Save processed image. + cudaMemcpy(pixel_arr, gpu_pixel_arr, pixel_struct_byte_count, cudaMemcpyDeviceToHost); + save(); + + // Reset class pixel array + for (int index = 0; index < image_total_pixels; index++) { + pixel_arr[index].r = pixel_arr_orig[index].r; + pixel_arr[index].g = pixel_arr_orig[index].g; + pixel_arr[index].b = pixel_arr_orig[index].b; + } + cudaMemcpy(gpu_pixel_arr, pixel_arr, pixel_struct_byte_count, cudaMemcpyHostToDevice); + + // Process image by iterating over all chunks for new image test. + for (int chunk_index = 0; chunk_index < total_image_chunks; chunk_index++) { + // Run GPU test logic to increase/decrease r/g/b colors in image. + set_file_name(base_file_name + "_green." + base_file_extension); + cuda_increase_green<<<cuda_device_max_blocks, cuda_device_max_threads>>>(image_total_pixels, cuda_chunk_size, chunk_index, gpu_pixel_arr); + + // Synchronize all GPU computations before iterating over next chunk. + cudaDeviceSynchronize(); + } + + // Save processed image. + cudaMemcpy(pixel_arr, gpu_pixel_arr, pixel_struct_byte_count, cudaMemcpyDeviceToHost); + save(); + + // Reset class pixel array + for (int index = 0; index < image_total_pixels; index++) { + pixel_arr[index].r = pixel_arr_orig[index].r; + pixel_arr[index].g = pixel_arr_orig[index].g; + pixel_arr[index].b = pixel_arr_orig[index].b; + } + cudaMemcpy(gpu_pixel_arr, pixel_arr, pixel_struct_byte_count, cudaMemcpyHostToDevice); + + // Process image by iterating over all chunks for new image test. + for (int chunk_index = 0; chunk_index < total_image_chunks; chunk_index++) { + // Run GPU test logic to increase/decrease r/g/b colors in image. + set_file_name(base_file_name + "_blue." + base_file_extension); + cuda_increase_blue<<<cuda_device_max_blocks, cuda_device_max_threads>>>(image_total_pixels, cuda_chunk_size, chunk_index, gpu_pixel_arr); + + // Synchronize all GPU computations before iterating over next chunk. + cudaDeviceSynchronize(); + } + + // Save processed image. + cudaMemcpy(pixel_arr, gpu_pixel_arr, pixel_struct_byte_count, cudaMemcpyDeviceToHost); + save(); + + // Retrieve results. + cudaMemcpy(pixel_arr, gpu_pixel_arr, pixel_struct_byte_count, cudaMemcpyDeviceToHost); + cudaFree(gpu_pixel_arr); + delete[] pixel_arr_orig; + + logger.debug("Image::compute() finished."); + logger.debug(""); +} + +//endregion Public Methods. diff --git a/part_0/image.h b/part_0/image.h new file mode 100644 index 0000000000000000000000000000000000000000..a64cc8c0de6647e480888f277773d4fa312fcbfc --- /dev/null +++ b/part_0/image.h @@ -0,0 +1,131 @@ +/** + * Header file for project image handling logic. + */ + + +// System Import Headers. +#include <cctype> +#include <cstdio> +#include <cstdlib> +#include <filesystem> +#include <fstream> +#include <iostream> +#include <iterator> +#include <string> +#include <sys/stat.h> +#include <sys/types.h> + + +/** + * Class that holds data for a single image. + */ +class Image { + + public: + /** + * Struct to act as a "container" of sorts for each individual rgb pixel. + * An array of these creates our entire image. + * + * According to https://stackoverflow.com/a/36917400, this is basically treated as a sub-class. + * This allows creating multiple constructors, which will handle different scenarios of pixel input. + */ + struct PixelStruct { + // Struct variables. + float r, g, b; + + /** + * Struct constructors. + * + * First one acts as a "default" that provides black pixels. + * When a single float is provided, the second constructor sets all three rgb channels to that value. + * Finally, when three floats are provided, the last constructor sets rgb channels accordingly. + */ + PixelStruct(): r(0), g(0), b(0) {} + PixelStruct(float c): r(c), g(c), b(c) {} + PixelStruct(float _r, float _g, float _b) : r(_r), g(_g), b(_b) {} + + }; + + private: + // Private Variables. + std::string input_path; + std::string output_path; + unsigned int width; + unsigned int height; + int bit_depth; + PixelStruct *pixel_arr; // Array of pixel data. + + /** + * Calculates class input_path and output_path values. + * + * :param image_path: Path to an image on the system. + */ + void calculate_paths(std::string image_path); + + + /** + * Imports image file data, using class variable data. + */ + void import_image_file(); + + + public: + // Public Variables. + static const PixelStruct pixelBlack, pixelWhite, pixelRed, pixelGreen, pixelBlue, pixelYellow, pixelPink, pixelLiBlue; + + + //region Constructors. + + /** + * Base constructor. + */ + Image(); + + /** + * Constructor that takes input path to image file. + * Output path is determined based on parsing input path. + */ + Image(std::string user_path); + + /** + * Deconstructor. + */ + ~Image(); + + //endregion Constructors. + + + //region Public Methods. + + /** + * Displays general properties of image held in class. + */ + void display_properties(); + + + /** + * Saves class image data to output file. + */ + void save(); + + + /** + * Gets current file name, using original inpute path value. + */ + std::string get_file_name(); + + + /** + * Changes output file name to provided value. + */ + void set_file_name(std::string file_name); + + + /** + * Method to actually do work on image. + */ + void compute(); + + //endregion Public Methods. + +}; diff --git a/part_0/logging.cu b/part_0/logging.cu new file mode 100644 index 0000000000000000000000000000000000000000..fc66c735c97e95044f1386727792786192e329cb --- /dev/null +++ b/part_0/logging.cu @@ -0,0 +1,162 @@ +/** + * File for project logging. + * Contains actual logic for class defined in logging.h. + */ + + +// User Import Headers. +#ifndef user_headers + #define user_headers + #define user_headers_logging + #include "logging.h" +#endif + + +/** + * Class constructor. + */ +Logging::Logging() { + std::filesystem::path cwd = std::filesystem::current_path(); + core_logging_folder = cwd.string() + "/logs/"; + + // Check if logging directory exists. + struct stat info; + if ( (stat(core_logging_folder.c_str(), &info) != 0) || (! info.st_mode & S_IFDIR)) { + // Directory does not exist. + mkdir(core_logging_folder.c_str(), 0777); + } + + // Open file descriptors. + error_stream.open(core_logging_folder + "error.log", std::ios::app | std::ios::out); + warn_stream.open(core_logging_folder + "warn.log", std::ios::app | std::ios::out); + info_stream.open(core_logging_folder + "info.log", std::ios::app | std::ios::out); + debug_stream.open(core_logging_folder + "debug.log", std::ios::app | std::ios::out); + timer_stream.open(core_logging_folder + "timer.log", std::ios::app | std::ios::out); +} + +/** + * Class deconstructor. + * Closes file descriptors, if open. + */ +Logging::~Logging() { + close_log_files(); +} + + +/** + * Closes all open logging file descriptors. + */ +void Logging::close_log_files() { + if (debug_stream.is_open()) { + debug_stream.close(); + } + if (info_stream.is_open()) { + info_stream.close(); + } + if (warn_stream.is_open()) { + warn_stream.close(); + } + if (error_stream.is_open()) { + error_stream.close(); + } + if (timer_stream.is_open()) { + timer_stream.close(); + } +} + + +/** + * Writes message to info log file. + */ +void Logging::debug(std::string msg, std::string log_type, const char* file, long line_num) { + // Build and write logging message. + std::string log_prefix = " [" + log_type + "] [ " + std::string(file) + " | " + std::to_string(line_num) + " ] "; + debug_stream << log_prefix << msg << std::endl; +} + + +/** + * Writes message to info log file. + */ +void Logging::info(std::string msg, std::string log_type, const char* file, long line_num) { + // Build and write logging message. + std::string log_prefix = " [" + log_type + "] [ " + std::string(file) + " | " + std::to_string(line_num) + " ] "; + info_stream << log_prefix << msg << std::endl; + + // Write to console. + std::cout << log_prefix << msg << std::endl; + + // Also write to lower level loggers. + debug(msg, log_type, file, line_num); +} + + +/** + * Writes message to warning log file. + */ +void Logging::warn(std::string msg, std::string log_type, const char* file, long line_num) { + // Build and write logging message. + std::string log_prefix = " [" + log_type + "] [ " + std::string(file) + " | " + std::to_string(line_num) + " ] "; + warn_stream << log_prefix << msg << std::endl; + + // Also write to lower level loggers. + info(msg, log_type, file, line_num); +} + + +/** + * Writes message to error log file. + */ +void Logging::error(std::string msg, std::string log_type, const char* file, long line_num) { + // Build and write logging message. + std::string log_prefix = " [" + log_type + "] [ " + std::string(file) + " | " + std::to_string(line_num) + " ] "; + error_stream << log_prefix << msg << std::endl; + + // Also write to lower level loggers. + warn(msg, log_type, file, line_num); +} + + +/** + * Writes message to timer log file. + */ +void Logging::timer(std::string msg, std::string log_type, const char* file, long line_num) { + // Build and write logging message. + std::string log_prefix = " [" + log_type + "] [ " + std::string(file) + " | " + std::to_string(line_num) + " ] "; + timer_stream << log_prefix << msg << std::endl; + + // Also write to info and lower level loggers. + info(msg, log_type, file, line_num); +} + + +/** + * Prints out logging location to console. + */ +void Logging::display_logging_folder() { + std::cout << "Logging folder: " << core_logging_folder << std::endl; +} + + +/** + * Resets contents of logging files. + */ +void Logging::clear_log_files() { + // First close any open logging file descriptors. + close_log_files(); + + // Open log files in "write mode", which will override any present data. + error_stream.open(core_logging_folder + "error.log", std::ios::out); + warn_stream.open(core_logging_folder + "warn.log", std::ios::out); + info_stream.open(core_logging_folder + "info.log", std::ios::out); + debug_stream.open(core_logging_folder + "debug.log", std::ios::out); + timer_stream.open(core_logging_folder + "timer.log", std::ios::out); + + // Close log files and reopen in "append mode". Probably not necessary but here to be safe. + close_log_files(); + error_stream.open(core_logging_folder + "error.log", std::ios::app | std::ios::out); + warn_stream.open(core_logging_folder + "warn.log", std::ios::app | std::ios::out); + info_stream.open(core_logging_folder + "info.log", std::ios::app | std::ios::out); + debug_stream.open(core_logging_folder + "debug.log", std::ios::app | std::ios::out); + timer_stream.open(core_logging_folder + "timer.log", std::ios::app | std::ios::out); +} diff --git a/part_0/logging.h b/part_0/logging.h new file mode 100644 index 0000000000000000000000000000000000000000..afa4aed8b8aff86fff23ec2e4f55aad15bd69554 --- /dev/null +++ b/part_0/logging.h @@ -0,0 +1,84 @@ +/** + * Header file for project logging. + */ + + +// System Import Headers. +#include <cstdio> +#include <cstdlib> +#include <cctype> +#include <execinfo.h> +#include <iostream> +#include <filesystem> +#include <fstream> +#include <string> +#include <sys/stat.h> +#include <sys/types.h> + + +/** + * Logging class definition. + */ +class Logging { + + private: + std::string core_logging_folder; + std::ofstream debug_stream; + std::ofstream info_stream; + std::ofstream warn_stream; + std::ofstream error_stream; + std::ofstream timer_stream; + + public: + /** + * Class constructor. + */ + Logging(); + + /** + * Class deconstructor. + * Closes file descriptors, if open. + */ + ~Logging(); + + /** + * Closes all open logging file descriptors. + */ + void close_log_files(); + + /** + * Writes message to info log file. + */ + void debug(std::string msg, std::string log_type = "DEBUG", const char* file = __builtin_FILE(), long line_num = __builtin_LINE()); + + /** + * Writes message to info log file. + */ + void info(std::string msg, std::string log_type = "INFO", const char* file = __builtin_FILE(), long line_num = __builtin_LINE()); + + /** + * Writes message to warning log file. + */ + void warn(std::string msg, std::string log_type = "WARN", const char* file = __builtin_FILE(), long line_num = __builtin_LINE()); + + /** + * Writes message to error log file. + */ + void error(std::string msg, std::string log_type = "ERROR", const char* file = __builtin_FILE(), long line_num = __builtin_LINE()); + + /** + * Writes message to timer log file. + */ + void timer(std::string msg, std::string log_type = "TIMER", const char* file = __builtin_FILE(), long line_num = __builtin_LINE()); + + + /** + * Prints out logging location to console. + */ + void display_logging_folder(); + + /** + * Resets contents of logging files. + */ + void clear_log_files(); +}; diff --git a/part_0/main.cu b/part_0/main.cu new file mode 100644 index 0000000000000000000000000000000000000000..d39f621936b5ebb674d9534652548d3915905c35 --- /dev/null +++ b/part_0/main.cu @@ -0,0 +1,179 @@ +/** + * Program entrypoint and intiialization. + */ + + +// System Import Headers. +#include <cctype> +#include <cstdio> +#include <cstdlib> +#include <ctime> +#include <filesystem> +#include <fstream> +#include <iostream> +#include <iterator> +#include <string> +#include <vector> + +// User Import Headers. +#ifndef user_headers + #define user_headers + #include "image.h" + #include "utility.h" +#endif + +#ifndef user_headers_logging + #define user_headers_logging + #include "logging.h" +#endif + + +// Method Declaration. +void process_dir(std::string path_str); +void process_file(std::string path_str); + + +// Global Variables. +#ifndef LOGGING_CLASS_INIT + #define LOGGING_CLASS_INIT + Logging logger; +#endif + + +/* + * Program's main. Initializes and runs program. + */ +int main(int argc, char* argv[]) { + printf("Starting program.\n"); + printf("\n"); + printf("\n"); + + // Initialize logging and clear any existing log contents for new program run. + logger.clear_log_files(); + + // Check provided number of args. + if (argc < 2) { + // No args provided. + printf("Provided too few program args. Expected one arg."); + } else if (argc > 2) { + // More than one arg provided. + printf("Provided too many program args. Expected one arg."); + } else { + // Correct number of args provided. Pass to image class for processing. + + // Check data type. + std::string path_str = argv[1]; + path_validator_struct* return_struct = validate_path(path_str); + int path_type = return_struct->file_type; + if (return_struct->err_code) { + std::cerr << "Error verifying file type: " << return_struct->err_code.message() << std::endl; + } + free_path_validator_struct(return_struct); + + // Handle based on provided type. + if (path_type == TYPE_DIR) { + // Process dir. + process_dir(path_str); + } else if (path_type == TYPE_FILE) { + // Process file. + process_file(path_str); + } + } + + printf("\n"); + printf("\n"); + printf("Terminating program.\n"); + exit(0); +} + + +/** + * Finds and processes all files in directory. + * Assumes directory is full of image files. + */ +void process_dir(std::string path_str) { + logger.debug("process_dir():"); + logger.debug(" path_str: " + path_str); + logger.debug(""); + + logger.timer(""); + logger.timer(""); + logger.timer(""); + logger.timer("Handling dir \"" + path_str + "\""); + + // Track time to process. + time_t start_time = time(0); + + // Process all files in directory. + for (const auto & entry : std::filesystem::directory_iterator(path_str)) { + // Get as string. + std::string file_path_str{entry.path().u8string()}; + // std::cout << file_path_str << std::endl; + + // Check if new path value is directory or file. + path_validator_struct* return_struct = validate_path(file_path_str); + int path_type = return_struct->file_type; + if (return_struct->err_code) { + std::cerr << "Error verifying file type: " << return_struct->err_code.message() << std::endl; + } + free_path_validator_struct(return_struct); + + // Handle based on provided type. + if (path_type == TYPE_DIR) { + // Process dir. + process_dir(file_path_str); + } else if (path_type == TYPE_FILE) { + // Process file. + process_file(file_path_str); + } + } + + // Calculate time processing dir. + double total_time = difftime( time(0), start_time); + logger.timer(""); + logger.timer("Time to Process Dir \"" + path_str + "\": "); + double seconds_elapsed = fmod(total_time, 60); + double minutes_elapsed = (total_time - seconds_elapsed) / 60; + std::ostringstream second_format_string; + std::ostringstream minute_format_string; + second_format_string << std::setprecision(0) << std::fixed << seconds_elapsed; + minute_format_string << std::setprecision(0) << std::fixed << minutes_elapsed; + logger.timer(minute_format_string.str() + " min, " + second_format_string.str() + " seconds"); +} + + +/** + * Processes a single image file. + */ +void process_file(std::string path_str) { + logger.debug("process_file():"); + logger.debug(" path_str: " + path_str); + logger.debug(""); + + logger.info(""); + logger.info(""); + logger.timer(""); + logger.timer("Handling file \"" + path_str + "\""); + + // Track time to process. + time_t start_time = time(0); + + // Import image data to class. + Image image(path_str); + image.display_properties(); + + // Manipulate and save image data. + image.compute(); + // image.save(); + + // Calculate time processing file. + double total_time = difftime( time(0), start_time); + logger.timer("Time to Process File \"" + path_str + "\": "); + double seconds_elapsed = fmod(total_time, 60); + double minutes_elapsed = (total_time - seconds_elapsed) / 60; + std::ostringstream second_format_string; + std::ostringstream minute_format_string; + second_format_string << std::setprecision(0) << std::fixed << seconds_elapsed; + minute_format_string << std::setprecision(0) << std::fixed << minutes_elapsed; + logger.timer(minute_format_string.str() + " min, " + second_format_string.str() + " seconds"); +} diff --git a/part_0/makefile b/part_0/makefile new file mode 100644 index 0000000000000000000000000000000000000000..85404c9c0a3d11d73de90e8331493db25fbbac65 --- /dev/null +++ b/part_0/makefile @@ -0,0 +1,36 @@ + +# Tell makefile to use commands defined here, if file with same name exists. +.PHONY: all run valgrind +# Set default if none is specified. +default: all + + +# Variables. +CC = nvcc +CFLAGS = -std=c++17 +TARGET = main.out +LIBRARIES = +DEPENDENCIES = ./main.cu ./image.cu ./logging.cu ./utility.cu +# ARGS = `arg="$(filter-out $@,$(MAKECMDGOALS))" && echo $${arg:-${1}}` +ARGS = $(filter-out $@,$(MAKECMDGOALS)) + + +# Compile target if any depenencies update. +$(TARGET): $(DEPENDENCIES) + @$(CC) $(CFLAGS) $(DEPENDENCIES) -o $(TARGET) $(LIBRARIES) + + +# Compile target if dependencies update. +all: $(TARGET) + + +# Compile target if dependencies update. Then run. +run: $(TARGET) + @./$(TARGET) $(ARGS) + + +# Compile target if dependencies update. Then run. +valgrind: $(TARGET) +# @rm ./logs/vg/* +# valgrind --leak-check=full --log-file=./logs/vg/vg.%p ./$(TARGET) $(ARGS) + valgrind --leak-check=full ./$(TARGET) $(ARGS) diff --git a/part_0/utility.cu b/part_0/utility.cu new file mode 100644 index 0000000000000000000000000000000000000000..d585b055a8e974d7e07d506485e8a842cd35839c --- /dev/null +++ b/part_0/utility.cu @@ -0,0 +1,88 @@ +/** + * File for utility logic. + */ + +// User Import Headers. +#ifndef user_headers + #define user_headers + #include "utility.h" +#endif + +#ifndef user_headers_logging + #define user_headers_logging + #include "logging.h" +#endif + + +// Global Variables. +#ifndef LOGGING_CLASS_INIT + #define LOGGING_CLASS_INIT + extern Logging logger; +#endif +const int TYPE_DIR = 0; +const int TYPE_FILE = 1; +const int TYPE_OTHER = 2; +path_validator_struct* path_validator_ptr; + + +/** + * Initializes a "path_validator" object. + */ +path_validator_struct* initialize_path_validator_struct(int file_type, std::error_code err_code) { + logger.debug("initialize_path_validator_struct():"); + logger.debug(" file_type: " + std::to_string(file_type)); + // logger.debug(" err_code: " + err_code); + logger.debug(""); + + // Create struct. + path_validator_ptr = (path_validator_struct*) calloc(1, sizeof(path_validator_struct)); + + // Populate fields. + path_validator_ptr->file_type = file_type; + path_validator_ptr->err_code = err_code; + + return path_validator_ptr; +} + + +/** + * Destroyes passed "path_validator" object and all associated fields. + */ +void free_path_validator_struct(path_validator_struct* path_validator_ptr) { + logger.debug("free_path_validator_struct():"); + // logger.debug(" path_validator_ptr: " + path_validator_ptr); + logger.debug(""); + + free(path_validator_ptr); +} + + +/** + * Validates provided path and if valid, determines path type. + * Returns struct of data. + */ +path_validator_struct* validate_path(std::string path_str) { + logger.debug("validate_path():"); + logger.debug(" path_str: " + path_str); + logger.debug(""); + + int type_value = TYPE_OTHER; + std::error_code ec; + + // Check if provided path exists. + std::ifstream test_path_exists(path_str); + if (test_path_exists) { + // Path exists. Check if dir or file. + if (std::filesystem::is_directory(path_str, ec)) { + // Is dir. + type_value = TYPE_DIR; + } else if ((! ec) && (std::filesystem::is_regular_file(path_str, ec))) { + // Is file. + type_value = TYPE_FILE; + } + } + + // Return values. + path_validator_struct* return_struct_ptr = initialize_path_validator_struct(type_value, ec); + return return_struct_ptr; +} diff --git a/part_0/utility.h b/part_0/utility.h new file mode 100644 index 0000000000000000000000000000000000000000..fea1b97f3423ee1aa6f6ef5e15b1678d65577628 --- /dev/null +++ b/part_0/utility.h @@ -0,0 +1,34 @@ +/** + * Header file for project utility logic. + */ + + +// System Import Headers. +#include <cctype> +#include <cstdio> +#include <cstdlib> +#include <filesystem> +#include <fstream> +#include <iostream> +#include <string> + + +// Global Variables. +extern const int TYPE_DIR; +extern const int TYPE_FILE; +extern const int TYPE_OTHER; + + +/** + * Data return for path validation function. + */ +typedef struct { + int file_type; + std::error_code err_code; +} path_validator_struct; + + +// // Function Prototypes. +path_validator_struct* initialize_path_validator_struct(int file_type, std::error_code err_code); +void free_path_validator_struct(path_validator_struct* path_validator_ptr); +path_validator_struct* validate_path(std::string path_str); diff --git a/part_1/image.cu b/part_1/image.cu index 9f0eb580c0a6fcaf2cf5106e5855d5bd20a4c68e..c77c4080a803ba691effacac99664347249b023f 100644 --- a/part_1/image.cu +++ b/part_1/image.cu @@ -78,201 +78,6 @@ void display_cuda_device_properties() { } } -/** - * Updates image so that every other pixel alternates between pure black and pure white. - * Purely for testing purposes. - */ -// __global__ void cuda_pixel_alternating(Image::PixelStruct* pixel_arr, int pixel_index) { -__global__ void cuda_alternating_pixels(unsigned long total_pixels, unsigned long chunk_size, int chunk_counter, Image::PixelStruct* pixel_arr) { - // Calculate current index. - unsigned long pixel_index = blockIdx.x * blockDim.x + threadIdx.x; - - // Adjust index for current chunk. - pixel_index += (chunk_size * chunk_counter); - - // Only proceed if GPU thread corresponds to a pixel in the image. Ignore all extra threads. - if (pixel_index <= total_pixels) { - - // Manipulate pixel data based on index. - if (pixel_index % 2 == 0) { - // Set to black. - pixel_arr[pixel_index].r = 0.f; - pixel_arr[pixel_index].g = 0.f; - pixel_arr[pixel_index].b = 0.f; - } else { - // Set to white. - pixel_arr[pixel_index].r = 255.f; - pixel_arr[pixel_index].g = 255.f; - pixel_arr[pixel_index].b = 255.f; - } - } -} - - -/** - * Updates image so that blocks of pixels alternate between pure black and pure white. - * Effectively, it's similar to cuda_alternating_pixels, except that all image sizes should look roughly the same. - * Purely for testing purposes. - */ -// __global__ void cuda_pixel_alternating(Image::PixelStruct* pixel_arr, int pixel_index) { -__global__ void cuda_alternating_blocks(unsigned long total_pixels, unsigned long chunk_size, int chunk_counter, int pixel_block_size, int image_width, int image_height, Image::PixelStruct* pixel_arr) { - // Calculate current index. - unsigned long pixel_index = blockIdx.x * blockDim.x + threadIdx.x; - - // Adjust index for current chunk. - pixel_index += (chunk_size * chunk_counter); - - // Only proceed if GPU thread corresponds to a pixel in the image. Ignore all extra threads. - if (pixel_index <= total_pixels) { - - // Calculate if falls under black or white pixel block. - int row = pixel_index / image_width; - int col = pixel_index % image_width; - int row_grouping = row / pixel_block_size; - int col_grouping = col / pixel_block_size; - float color; - if (row_grouping % 2 == 0) { - if ((col_grouping % 2) == 0) { - color = 255.f; - } else { - color = 0.f; - } - } else { - if ((col_grouping % 2) == 0) { - color = 0.f; - } else { - - color = 255.f; - } - } - - // Set pixel color. - pixel_arr[pixel_index].r = color; - pixel_arr[pixel_index].g = color; - pixel_arr[pixel_index].b = color; - - // int color = row * col * 2; - // printf("Width: %d Height: %d Index: %ld Row: %d Col: %d Color: %d\n", image_width, image_height, pixel_index, row, col, color); - // pixel_arr[pixel_index].r = static_cast<float>(color) / 255.f; - // pixel_arr[pixel_index].g = static_cast<float>(color) / 255.f; - // pixel_arr[pixel_index].b = static_cast<float>(color) / 255.f; - } -} - - -/** - * Updates image to increase percieved value of red in image. - * Purely for testing purposes. - */ -__global__ void cuda_increase_red(unsigned long total_pixels, unsigned long chunk_size, int chunk_counter, Image::PixelStruct* pixel_arr) { - // Calculate current index. - unsigned long pixel_index = blockIdx.x * blockDim.x + threadIdx.x; - - // Adjust index for current chunk. - pixel_index += (chunk_size * chunk_counter); - - // Only proceed if GPU thread corresponds to a pixel in the image. Ignore all extra threads. - if (pixel_index <= total_pixels) { - - // Calculate new pixel colors. - float red = pixel_arr[pixel_index].r + 0.1f; - float green = pixel_arr[pixel_index].g - 0.1f; - float blue = pixel_arr[pixel_index].b - 0.1f; - - // Handle if colors exceed expected values. - if (red > 1.0f) { - red = 1.f; - } - if (green < 0.0f) { - green = 0.f; - } - if (blue < 0.0f) { - blue = 0.f; - } - - // Update pixel values. - pixel_arr[pixel_index].r = red; - pixel_arr[pixel_index].g = green; - pixel_arr[pixel_index].b = blue; - } -} - - -/** - * Updates image to increase percieved value of green in image. - * Purely for testing purposes. - */ -__global__ void cuda_increase_green(unsigned long total_pixels, unsigned long chunk_size, int chunk_counter, Image::PixelStruct* pixel_arr) { - // Calculate current index. - unsigned long pixel_index = blockIdx.x * blockDim.x + threadIdx.x; - - // Adjust index for current chunk. - pixel_index += (chunk_size * chunk_counter); - - // Only proceed if GPU thread corresponds to a pixel in the image. Ignore all extra threads. - if (pixel_index <= total_pixels) { - - // Calculate new pixel colors. - float red = pixel_arr[pixel_index].r - 0.1f; - float green = pixel_arr[pixel_index].g + 0.1f; - float blue = pixel_arr[pixel_index].b - 0.1f; - - // Handle if colors exceed expected values. - if (red < 0.0f) { - red = 0.f; - } - if (green > 1.0f) { - green = 1.f; - } - if (blue < 0.0f) { - blue = 0.f; - } - - // Update pixel values. - pixel_arr[pixel_index].r = red; - pixel_arr[pixel_index].g = green; - pixel_arr[pixel_index].b = blue; - } -} - - -/** - * Updates image to increase percieved value of blue in image. - * Purely for testing purposes. - */ -__global__ void cuda_increase_blue(unsigned long total_pixels, unsigned long chunk_size, int chunk_counter, Image::PixelStruct* pixel_arr) { - // Calculate current index. - unsigned long pixel_index = blockIdx.x * blockDim.x + threadIdx.x; - - // Adjust index for current chunk. - pixel_index += (chunk_size * chunk_counter); - - // Only proceed if GPU thread corresponds to a pixel in the image. Ignore all extra threads. - if (pixel_index <= total_pixels) { - - // Calculate new pixel colors. - float red = pixel_arr[pixel_index].r - 0.1f; - float green = pixel_arr[pixel_index].g - 0.1f; - float blue = pixel_arr[pixel_index].b + 0.1f; - - // Handle if colors exceed expected values. - if (red < 0.0f) { - red = 0.f; - } - if (green < 0.0f) { - green = 0.f; - } - if (blue > 1.0f) { - blue = 1.f; - } - - // Update pixel values. - pixel_arr[pixel_index].r = red; - pixel_arr[pixel_index].g = green; - pixel_arr[pixel_index].b = blue; - } -} - /** * Uses convolution to blur image. @@ -504,7 +309,7 @@ void Image::calculate_paths(std::string image_path) { // // Calculate output path location. output_path = "../output/"; - std::string base_filename = image_path.substr(image_path.find_last_of("/\\") + 1); + std::string base_filename = get_file_name();; // Grab directory substrings and generate full output path. size_t pos = 0; @@ -681,6 +486,30 @@ void Image::save() { } +/** + * Gets current file name, using original inpute path value. + */ +std::string Image::get_file_name() { + logger.debug("Image::get_file_name():"); + logger.debug(""); + + return input_path.substr(input_path.find_last_of("/\\") + 1); +} + + +/** + * Changes output file name to provided value. + */ +void Image::set_file_name(std::string file_name) { + logger.debug("Image::compute():"); + logger.debug(" file_name: " + file_name); + logger.debug(""); + + std::string new_path = output_path.substr(0, output_path.find_last_of("/\\") + 1); + output_path = new_path + file_name; +} + + /** * Method to actually do work on image. */ @@ -697,24 +526,13 @@ void Image::compute() { int cuda_device_max_threads = cuda_device_properties.maxThreadsPerBlock; unsigned long cuda_chunk_size = cuda_device_max_blocks * cuda_device_max_threads; - logger.info("CUDA Max Blocks: " + std::to_string(cuda_device_max_blocks)); - logger.info("CUDA Max Threads: " + std::to_string(cuda_device_max_threads)); - logger.info("CUDA Chunk Size: " + std::to_string(cuda_chunk_size)); - logger.info("CUDA Global Mem: " + std::to_string(cuda_device_properties.totalGlobalMem)); - // Calculate some image data. int pixel_struct_size = sizeof(PixelStruct); unsigned long image_total_pixels = width * height; unsigned long pixel_struct_byte_count = image_total_pixels * pixel_struct_size; - logger.info("Total Image Size: " + std::to_string(image_total_pixels)); - logger.info("Pixel Struct Size: " + std::to_string(pixel_struct_byte_count)); - // We handle image in chunks, in case we're dealing with large images with more pixels than CUDA device blocks/threads. int total_image_chunks = (image_total_pixels / cuda_chunk_size) + 1; - logger.info("Total Image Chunks: " + std::to_string(total_image_chunks)); - logger.info("Desired CUDA block count: " + std::to_string(cuda_device_max_blocks)); - logger.info("Desired CUDA thread count: " + std::to_string(cuda_device_max_threads)); // Generate convolution mask dimensions. Should be either 10% of image size or 3 pixels large, whichever is greater. int mask_width = width / 10; @@ -784,21 +602,6 @@ void Image::compute() { logger.info("Iterating over image chunk " + std::to_string(chunk_index)); logger.info("Accounts for pixels " + std::to_string(cuda_chunk_size * chunk_index) + " through " + std::to_string((cuda_chunk_size * chunk_index) + cuda_chunk_size) ); - // Run GPU test logic to set alternating pixels to black/white. - // cuda_alternating_pixels<<<cuda_device_max_blocks, cuda_device_max_threads>>>(image_total_pixels, cuda_chunk_size, chunk_index, gpu_pixel_arr); - - // Run GPU test logic to create black/white checkerboard pattern. - // int pixel_block_size = width / 10; - // if (pixel_block_size <= 1) { - // pixel_block_size = 2; - // } - // cuda_alternating_blocks<<<cuda_device_max_blocks, cuda_device_max_threads>>>(image_total_pixels, cuda_chunk_size, chunk_index, pixel_block_size, width, height, gpu_pixel_arr); - - // Run GPU test logic to increase/decrease r/g/b colors in image.. - // cuda_increase_red<<<cuda_device_max_blocks, cuda_device_max_threads>>>(image_total_pixels, cuda_chunk_size, chunk_index, gpu_pixel_arr); - // cuda_increase_green<<<cuda_device_max_blocks, cuda_device_max_threads>>>(image_total_pixels, cuda_chunk_size, chunk_index, gpu_pixel_arr); - // cuda_increase_blue<<<cuda_device_max_blocks, cuda_device_max_threads>>>(image_total_pixels, cuda_chunk_size, chunk_index, gpu_pixel_arr); - // Run GPU logic to use convolution to create blur effect. cuda_convolution_blur<<<cuda_device_max_blocks, cuda_device_max_threads>>>(image_total_pixels, cuda_chunk_size, chunk_index, width, height, mask_width, mask_height, gpu_pixel_arr_orig, gpu_pixel_arr, gpu_mask_arr); @@ -809,6 +612,9 @@ void Image::compute() { // Retrieve results. cudaMemcpy(pixel_arr, gpu_pixel_arr, pixel_struct_byte_count, cudaMemcpyDeviceToHost); cudaFree(gpu_pixel_arr); + cudaFree(gpu_pixel_arr_orig); + cudaFree(gpu_mask_arr); + delete[] mask_arr; logger.debug("Image::compute() finished."); logger.debug(""); diff --git a/part_1/image.h b/part_1/image.h index df24cba921ba7cfd0f4feffa3ff66552b6001e39..a64cc8c0de6647e480888f277773d4fa312fcbfc 100644 --- a/part_1/image.h +++ b/part_1/image.h @@ -109,6 +109,18 @@ class Image { void save(); + /** + * Gets current file name, using original inpute path value. + */ + std::string get_file_name(); + + + /** + * Changes output file name to provided value. + */ + void set_file_name(std::string file_name); + + /** * Method to actually do work on image. */